@com55/node-red-ai-access 0.2.0-beta.6 → 0.2.0-beta.7
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/CHANGELOG.md +18 -0
- package/README.md +50 -10
- package/ai-access.html +1 -1
- package/dist/plugin.cjs +464 -312
- package/dist/plugin.d.cts +86 -6
- package/docs/api/openapi.yaml +228 -30
- package/docs/errors.md +16 -5
- package/docs/mcp-tools.md +18 -7
- package/package.json +1 -1
package/dist/plugin.cjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var Vc=Object.create;var qn=Object.defineProperty;var Yc=Object.getOwnPropertyDescriptor;var Xc=Object.getOwnPropertyNames;var Zc=Object.getPrototypeOf,Qc=Object.prototype.hasOwnProperty;var a=(e,t)=>qn(e,"name",{value:t,configurable:!0});var q=(e,t)=>()=>(e&&(t=e(e=0)),t);var Ki=(e,t)=>{for(var n in t)qn(e,n,{get:t[n],enumerable:!0})},Ji=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Xc(t))!Qc.call(e,o)&&o!==n&&qn(e,o,{get:()=>t[o],enumerable:!(r=Yc(t,o))||r.enumerable});return e};var zn=(e,t,n)=>(n=e!=null?Vc(Zc(e)):{},Ji(t||!e||!e.__esModule?qn(n,"default",{value:e,enumerable:!0}):n,e)),eu=e=>Ji(qn({},"__esModule",{value:!0}),e);function ft(e,t,n){let r=n.pretty?`${JSON.stringify(e,null,2)}
|
|
2
2
|
`:`${JSON.stringify(e)}
|
|
3
|
-
`,o=Buffer.from(r,"utf8");if(o.byteLength>t)throw new Y(n.kind,o.byteLength,t);return{bytes:o}}function Ds(e,t){let n=(0,wn.resolve)(t),r=(0,wn.resolve)(e);if(r!==n&&!r.startsWith(n+wn.sep))throw new Error("Path escapes .ai-access storage root");return r}function Ot(e,t,n){let r=Ds(t,e),o=`${r}.${process.pid}.tmp`;(0,Be.writeFileSync)(o,n.bytes,{mode:er}),(0,Be.renameSync)(o,r),(0,Be.chmodSync)(r,er)}function js(e,t,n){let r=Ds(t,e);(0,Be.appendFileSync)(r,n.bytes,{mode:er});try{(0,Be.chmodSync)(r,er)}catch{}}var Be,wn,ko,er,H,Ms,Y,G=U(()=>{"use strict";Be=require("fs"),wn=require("path"),ko=448,er=384,H=15e5,Ms=8192,Y=class extends Error{constructor(n,r,o){super(`${n} size ${r} bytes exceeds limit of ${o} bytes`);this.kind=n;this.size=r;this.maxBytes=o;this.name="StoragePayloadTooLargeError"}static{a(this,"StoragePayloadTooLargeError")}code="payload_too_large";statusCode=413};a(Je,"encodeTrustedJson");a(Ds,"assertUnderAiAccess");a(Ot,"writeTrustedAtomic");a(js,"appendTrusted")});function vn(e){if(typeof e!="string"||e.length===0||e.length>2048)return;let t;try{t=new URL(e)}catch{return}if(!(t.protocol!=="http:"&&t.protocol!=="https:")&&!(t.username||t.password||t.search||t.hash)&&!(t.pathname!=="/"||/^[a-z][a-z0-9+.-]*:\/\/[^/]*\//i.test(e)))return t.origin.toLowerCase()}function Ll(e,t){let n=e.getHeader("Vary"),r=String(n??"").split(",").map(o=>o.trim()).filter(Boolean);r.some(o=>o.toLowerCase()===t.toLowerCase())||r.push(t),e.setHeader("Vary",r.join(", "))}function Us(e){return(t,n,r)=>{if(!_l.test(t.path)){r();return}for(let c of n.getHeaderNames())c.startsWith("access-control-")&&n.removeHeader(c);let o=n.setHeader.bind(n),s=!1;n.setHeader=((c,u)=>!s&&/^access-control-/i.test(c)?n:o(c,u));let i=vn(t.get("origin")),d=i===void 0?void 0:e().find(c=>c===i),l=d!==void 0;if(Ll(n,"Origin"),!l){if(t.method==="OPTIONS"&&t.get("access-control-request-method")){n.status(204).end();return}r();return}if(s=!0,n.setHeader("Access-Control-Allow-Origin",d),n.setHeader("Access-Control-Expose-Headers",Cl),t.method==="OPTIONS"&&t.get("access-control-request-method")){n.setHeader("Access-Control-Allow-Methods",Pl),n.setHeader("Access-Control-Allow-Headers",El),n.setHeader("Access-Control-Max-Age",String(600)),n.status(204).end();return}s=!1,r()}}function Bs(e,t){let n=e.parent,r=n&&typeof n.use=="function"?n:e;r.use(t);let o=r,i=(o._router??o.router)?.stack;if(!Array.isArray(i)||i.length<2)return;let d=i.pop(),l=0;for(;l<i.length;){let c=i[l].name;if(c!=="query"&&c!=="expressInit")break;l+=1}i.splice(l,0,d)}var _l,Pl,El,Cl,rr=U(()=>{"use strict";_l=/^\/mcp(?:\/?$|\/(?:a(?:\/|$)|api(?:\/|$)|rpc\/?$))/,Pl="GET, POST, DELETE, OPTIONS",El="Authorization, Content-Type, Accept, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID",Cl="Mcp-Session-Id, WWW-Authenticate, Retry-After, RateLimit, RateLimit-Policy, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset";a(vn,"canonicalOrigin");a(Ll,"appendVary");a(Us,"aiCorsMiddleware");a(Bs,"installAiCorsMiddleware")});function sr(e){return e.replace(/([a-z])([A-Z])/g,"$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").replace(/([A-Za-z])([0-9])/g,"$1 $2").replace(/([0-9])([A-Za-z])/g,"$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean).map(t=>t.toUpperCase())}function An(e){if(!Array.isArray(e))return;let t=[];for(let n of e){if(typeof n!="string")continue;let r=n.trim().toUpperCase();if(!(!or.test(r)||t.includes(r))&&(t.push(r),t.length>=kn))break}return t}var te,ct,or,kn,Sn=U(()=>{"use strict";te="[REDACTED]",ct=Object.freeze(["PASSWORD","PASSWD","PASS","PWD","PW","PASSPHRASE","SECRET","CLIENT_SECRET","TOKEN","API_KEY","APIKEY","KEY","PRIVATE","CREDENTIAL","AUTH","AUTHORIZATION","BEARER","SESSION","COOKIE","CONNECTION_STRING","CONN_STR","CONN","DSN"]),or=/^[A-Za-z0-9_-]{2,40}$/,kn=100;a(sr,"tokenizeName");a(An,"normalizeSecretNamePatterns")});function Nl(e){return e==="nodes"||e==="flows"||e==="full"}function ir(e,t){if(!e||typeof e!="object"||Array.isArray(e))return{};let n=e,r={};if(typeof n.enabled=="boolean"&&(r.enabled=n.enabled),typeof n.allowWithoutAdminAuth=="boolean"&&(r.allowWithoutAdminAuth=n.allowWithoutAdminAuth),typeof n.defaultLinkTtlSeconds=="number"&&Number.isFinite(n.defaultLinkTtlSeconds)&&n.defaultLinkTtlSeconds>0&&(r.defaultLinkTtlSeconds=Math.floor(n.defaultLinkTtlSeconds)),Nl(n.defaultDeploymentType)&&(r.defaultDeploymentType=n.defaultDeploymentType),typeof n.maxActiveLinks=="number"&&Number.isFinite(n.maxActiveLinks)&&n.maxActiveLinks>0&&(r.maxActiveLinks=Math.floor(n.maxActiveLinks)),typeof n.publicBaseUrl=="string")try{r.publicBaseUrl=Dt(n.publicBaseUrl)}catch{t?.(`AI Access: invalid publicBaseUrl value ${JSON.stringify(n.publicBaseUrl)} was dropped`)}if(Array.isArray(n.corsAllowedOrigins)){let o=new Set;for(let s of n.corsAllowedOrigins){let i=vn(s);i?o.add(i):t?.(`AI Access: invalid CORS origin value ${JSON.stringify(s)} was dropped`)}r.corsAllowedOrigins=[...o]}for(let o of["rateLimitWindowMs","rateLimitMaxPerIp","rateLimitMaxPerToken"]){let s=n[o],i=o==="rateLimitWindowMs"?100:1,d=o==="rateLimitWindowMs"?36e5:1e6;typeof s=="number"&&Number.isInteger(s)&&s>=i&&s<=d&&(r[o]=s)}if(n.secretNamePatterns!==void 0){let o=An(n.secretNamePatterns);o?(Array.isArray(n.secretNamePatterns)&&o.length<n.secretNamePatterns.length&&t?.("AI Access: invalid or duplicate secretNamePatterns entries were dropped"),r.secretNamePatterns=o):t?.("AI Access: secretNamePatterns must be a list of strings; ignored")}if(n.oauth&&typeof n.oauth=="object"&&!Array.isArray(n.oauth)){let o=n.oauth;r.oauth={},typeof o.enabled=="boolean"&&(r.oauth.enabled=o.enabled);for(let i of["accessTokenTtlSec","refreshIdleTtlSec","dcrMaxClients"]){let d=o[i];typeof d=="number"&&Number.isInteger(d)&&d>0&&d<=31536e3&&(r.oauth[i]=d)}let s=o.refreshMaxLifetimeSec;typeof s=="number"&&Number.isInteger(s)&&s>=0&&s<=31536e4&&(r.oauth.refreshMaxLifetimeSec=s)}return r}function Fl(e,t){if(!(0,Tn.existsSync)(e))return{};try{let n=(0,Tn.readFileSync)(e,"utf8");return ir(JSON.parse(n),t)}catch(n){return t?.(`AI Access: config.json is corrupt or unreadable; defaults are used (${String(n)})`),{}}}function ar(e,t){let n={},r={...xn};for(let s of Object.keys(xn).filter(i=>i!=="oauth"))n[s]=e[s]!==void 0?"settings.js":t[s]!==void 0?"disk":"default",Object.assign(r,{[s]:e[s]??t[s]??xn[s]});let o={...r,sources:n};return e.oauth!==void 0||t.oauth!==void 0?(o.oauth={...$t,...t.oauth??{},...e.oauth??{}},n.oauth=e.oauth!==void 0?"settings.js":"disk"):(o.oauth={...$t},n.oauth="default"),o}function xo(e,t){let n=ir(e??{},t);for(let o of Object.keys(xn).filter(s=>s!=="oauth"))e&&Object.hasOwn(e,o)&&n[o]===void 0&&Object.assign(n,{[o]:xn[o]});let r=e?.oauth;if(r&&typeof r=="object"&&!Array.isArray(r)){let o=n.oauth??={};for(let s of Object.keys($t))Object.hasOwn(r,s)&&o[s]===void 0&&(t?.(`AI Access: settings.js aiAccess.oauth.${s} is invalid; using the default (${String($t[s])})`),Object.assign(o,{[s]:$t[s]}))}return n}var Tn,So,Il,$t,xn,Mt,dr=U(()=>{"use strict";Tn=require("fs"),So=require("path");ce();rr();Sn();G();Il=16384,$t={enabled:!1,accessTokenTtlSec:3600,refreshIdleTtlSec:2592e3,refreshMaxLifetimeSec:7776e3,dcrMaxClients:5},xn={enabled:!0,allowWithoutAdminAuth:!1,defaultLinkTtlSeconds:14400,maxActiveLinks:10,defaultDeploymentType:"nodes",publicBaseUrl:"",corsAllowedOrigins:[],rateLimitWindowMs:6e4,rateLimitMaxPerIp:600,rateLimitMaxPerToken:300,secretNamePatterns:[...ct],oauth:{...$t}};a(Nl,"isDeploymentType");a(ir,"sanitizePartial");a(Fl,"readDiskFile");a(ar,"mergeAiAccessSettings");a(xo,"settingsJsPartial");Mt=class{constructor(t,n,r){this.paths=t;this.filePath=(0,So.join)(t.root,"config.json"),this.settingsJs=xo(n,r),this.disk=Fl((0,Tn.existsSync)(this.filePath)?this.filePath:(0,So.join)(t.root,"settings.json"),r),this.cached=ar(this.settingsJs,this.disk)}static{a(this,"SettingsStore")}filePath;disk;settingsJs;cached;getEffective(){return this.cached}getDisk(){return{...this.disk}}update(t){let n=ir(t);for(let r of Object.keys(this.settingsJs))delete n[r];return this.persist(n),this.disk=n,this.cached=ar(this.settingsJs,this.disk),this.cached}patch(t,n=[]){let r=ir(t),o={...this.disk,...r,...r.oauth?{oauth:{...this.disk.oauth,...r.oauth}}:{}};for(let s of n)delete o[s];return this.update(o)}persist(t){let n=Je(t,Il,{kind:"settings",pretty:!0});Ot(this.paths.root,this.filePath,n)}}});function cr(e){lr=e}function qs(){return lr}function z(e){return e.adminAuth!=null&&e.adminAuth!==!1}function ve(e){return lr?lr.getEffective():ar(xo(e.aiAccess),{})}function ke(e){let t=ve(e);return t.enabled?z(e)?!0:t.allowWithoutAdminAuth:!1}var lr,ue=U(()=>{"use strict";dr();lr=null;a(cr,"bindSettingsStore");a(qs,"getSettingsStore");a(z,"isAdminAuthEnabled");a(ve,"resolveAiAccessSettings");a(ke,"areAiFeaturesEnabled")});function pe(e){let t=`/${e??""}`.replace(/\/{2,}/g,"/");for(;t.length>1&&t.endsWith("/");)t=t.slice(0,-1);return t}function fe(e){let t=pe(e);return t==="/"?"/mcp/api/v1":`${t}/mcp/api/v1`}function Rn(e){let t=pe(e);return t==="/"?"/ai-access":`${t}/ai-access`}function Ve(e){let t=pe(e);return t==="/"?"/mcp/rpc":`${t}/mcp/rpc`}function Ol(e){let t=e.authInfo?.scope;if(t)return Array.isArray(t)?t:[t];let n=e.user?.permissions;return n?Array.isArray(n)?n:[n]:[]}function Ye(e){if(e.auth)return e.auth.scopes.includes("propose")?"propose":e.auth.scopes.includes("read")?"read":null;let t=Ol(e);return t.includes("*")?"*":t.includes("read")?"read":null}function $l(e,t){return!t||!e?{view:!0,manageLinks:!1,approveProposals:!1}:e==="*"?{view:!0,manageLinks:!0,approveProposals:!0}:{view:!0,manageLinks:!1,approveProposals:!1}}function zs(e,t={error:"Authentication required",code:"auth_required"},n){let r=e.end.bind(e),o=!0;return e.end=((...s)=>o&&e.statusCode===401&&!e.headersSent?(o=!1,e.removeHeader("Content-Length"),e.setHeader("Content-Type","application/json; charset=utf-8"),n&&e.setHeader("WWW-Authenticate",n),r(JSON.stringify(t))):r(...s)),()=>{o=!1,e.end=r}}function Ae(e,t){let n="read";return(r,o,s)=>{if(!z(e.settings)){s();return}let i=t==="api"?zs(o):null;e.auth.needsPermission(n)(r,o,d=>{if(i?.(),d){s(d);return}if(!r.user){if(t==="page"){o.redirect(pe(e.settings.httpAdminRoot));return}o.status(401).json({error:"Authentication required",code:"auth_required"});return}if(!Ye(r)){o.status(401).json({error:"Insufficient permissions",code:"insufficient_permissions"});return}s()})}}function ur(e,t){let n=z(e.settings),r=ve(e.settings),o=ke(e.settings),s=null;return n?s=Ye(t):o&&(s="*"),{adminAuthEnabled:n,aiFeaturesEnabled:o,allowWithoutAdminAuth:r.allowWithoutAdminAuth,permission:s,capabilities:$l(s,o)}}function jt(e){return(t,n,r)=>{if(!ke(e.settings)){n.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled"});return}r()}}function _n(e){return(t,n,r)=>{if(!z(e.settings)){if(ke(e.settings)){r();return}n.status(403).json({error:"AI Access features are disabled",code:"ai_features_disabled"});return}if(Ye(t)!=="*"){n.status(403).json({error:"Full admin permission (*) is required for this action",code:"insufficient_permissions"});return}r()}}function Ml(e){return e.startsWith("aia_")}function Dl(e){return Ml(e)?!0:e.length<24}function jl(e){if(e.length<24||e.startsWith("aia_")&&e.length<40)return"The token may be truncated \u2014 ask the user for the full access-link token."}function Ws(e,t,n,r){if(!z(e.settings)){r();return}let o={error:me,code:Ie},s=zs(n,o,X);e.auth.needsPermission("read")(t,n,i=>{if(s(),i){r(i);return}if(!n.headersSent){if(!t.user||!Ye(t)){n.setHeader("WWW-Authenticate",X),n.status(401).json(o);return}r()}})}function Gs(e,t,n,r,o){let s=e.headers.authorization;if(typeof s!="string"||!/^Bearer\s+\S+/i.test(s.trim()))return"continue";let d=/^Bearer\s+(\S+)$/i.exec(s.trim())?.[1];if(!d)return"continue";let l=n(d);if(l.status==="active")return l.link.kind!==r?(Cn(t,r,l.link.kind),"wrong_kind"):(e.accessLink=l.link,"ok");if(l.status==="expired")return En(e,t,o,l.expiresAt),"invalid";if(Dl(d)){t.setHeader("WWW-Authenticate",X);let c=jl(d);return t.status(401).json({error:ut,code:Pn,...c?{hint:c}:{}}),"invalid"}return"continue"}function Ks(e,t,n="rest"){return(r,o,s)=>{if(!ke(e.settings)){o.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled"});return}let i=Gs(r,o,t,n,e.settings.httpAdminRoot);if(i==="ok"){s();return}i==="invalid"||i==="wrong_kind"||Ws(e,r,o,s)}}function Js(e,t,n="rest"){return(r,o,s)=>{let i=Gs(r,o,t,n,e.settings.httpAdminRoot);if(i==="ok"){s();return}i==="invalid"||i==="wrong_kind"||Ws(e,r,o,s)}}var Le=U(()=>{"use strict";pt();ue();a(pe,"normalizeHttpAdminRoot");a(fe,"mcpApiBase");a(Rn,"dashboardPath");a(Ve,"mcpRpcPath");a(Ol,"scopesFromRequest");a(Ye,"resolvePermission");a($l,"capabilitiesFor");a(zs,"interceptNeedsPermissionUnauthorized");a(Ae,"requireAdminSession");a(ur,"describeAccess");a(jt,"requireAiFeatures");a(_n,"requireStarPermission");a(Ml,"looksLikeAccessLinkToken");a(Dl,"looksLikeFailedAccessLinkAttempt");a(jl,"truncatedTokenHint");a(Ws,"continueAsAdminSessionOrAccessLinkMissing");a(Gs,"tryAccessLinkBearer");a(Ks,"requireAdminReadOrAccessLink");a(Js,"requireAdminSessionOrAccessLink")});function Hl(e,t){let n=pe(e);return`${n==="/"?"/mcp/a":`${n}/mcp/a`}/${t}`}function Ul(e){return!!e.app.get("trust proxy")}function Vs(e,t){let n=e.get(t);if(typeof n!="string"||n.length===0)return;let r=n.split(",")[0]?.trim();return r&&r.length>0?r:void 0}function Bl(e){if(typeof e!="string"||e.trim().length===0)return;let t;try{t=new URL(e.trim())}catch{return}if(!(t.protocol!=="http:"&&t.protocol!=="https:"))return`${t.origin}${t.pathname.replace(/\/+$/,"")}`}function ft(e){try{let n=new URL(`http://${e.includes(":")&&!e.startsWith("[")?`[${e}]`:e}`).hostname.replace(/^\[|\]$/g,"");if(!(0,To.isIP)(n))return;if(n.startsWith("::ffff:")){let r=n.slice(7);if((0,To.isIP)(r)===4)return r;let o=r.split(":");if(o.length===2){let s=parseInt(o[0],16)*65536+parseInt(o[1],16);return[24,16,8,0].map(i=>s>>>i&255).join(".")}}return n}catch{return}}function Dt(e){if(e==="")return"";let t="publicBaseUrl must be an HTTP(S) origin with no path, query, fragment, or credentials";if(typeof e!="string")throw new Error(t);let n;try{n=new URL(e.trim())}catch{throw new Error(t)}if(n.protocol!=="https:"&&n.protocol!=="http:"||!/^https?:\/\/[^/?#]+\/?$/i.test(e.trim())||n.pathname!=="/"||n.search||n.hash||n.username||n.password||e.includes("?")||e.includes("#"))throw new Error(t);return n.origin}function pr(e){Ro=Bl(e)}function Xs(e){if(typeof e!="string"||e.length===0||e.length>260)return!1;let t,n,r=/^(\[[^\]]*\])(?::(\d+))?$/.exec(e);if(r){if(t=r[1],n=r[2],!Wl.test(t))return!1;try{new URL(`http://${t}/`)}catch{return!1}}else{let o=/^([^:]+)(?::(\d+))?$/.exec(e);if(!o||(t=o[1],n=o[2],t.length>253)||!zl.test(t)&&!ql.test(t))return!1}if(n!==void 0){if(!/^\d{1,5}$/.test(n))return!1;let o=Number(n);if(o<1||o>65535)return!1}return!0}function Qs(e){let t=e?.trim().toLowerCase();return t==="http"||t==="https"?t:void 0}function Gl(e){let t=e.socket;return t?.encrypted?"https":t?"http":Qs(e.protocol)??"http"}function _o(e){if(Ro)return Ro;let t=Ul(e),n=t?Vs(e,"x-forwarded-host"):void 0,r=e.get("host")?.trim(),o=Xs(n)?n:Xs(r)?r:void 0;if(!o)return;let s=(t?Qs(Vs(e,"x-forwarded-proto")):void 0)??Gl(e);try{return new URL(`${s}://${o}`).origin}catch{return}}function Ln(e,t){let n=_o(e);return n?`${n}${t}`:t}function ei(e,t,n){return Ln(e,Hl(t,n))}function Po(e){return e.replace(/\/mcp\/a\/[^/?#]+/gi,"/mcp/a/<redacted>")}var To,Ht,Zs,Ro,Ys,ql,zl,Wl,ce=U(()=>{"use strict";To=require("net");Le();Ht=8192,Zs=1800*1e3;a(Hl,"mcpAccessLinkPath");a(Ul,"isTrustProxyEnabled");a(Vs,"firstForwardedValue");a(Bl,"normalizePublicBaseUrl");a(ft,"canonicalizeIp");a(Dt,"validateSettingsPublicBaseUrl");a(pr,"bindPublicBaseUrl");Ys="[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?",ql=new RegExp(`^${Ys}(?:\\.${Ys})*$`),zl=/^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/,Wl=/^\[[0-9A-Fa-f:.]+\]$/;a(Xs,"isValidHostHeader");a(Qs,"validScheme");a(Gl,"connectionScheme");a(_o,"requestPublicOrigin");a(Ln,"absolutizePath");a(ei,"buildAccessLinkUrl");a(Po,"redactAccessLinkTokenInPath")});function Zl(e){if(!e||e.trim().length===0)return{kind:"missing"};let t=e.trim();if(/^Basic\s+/i.test(t))return{kind:"basic"};let n=/^Bearer\s+(\S+)$/i.exec(t);return n?.[1]?{kind:"bearer",token:n[1]}:{kind:"other"}}function Eo(e,t,n,r){e.setHeader("WWW-Authenticate",X),e.status(401).json({error:t,code:n,...r?{hint:r}:{}})}function Ql(e){let t=e??"";for(;t.endsWith("/");)t=t.slice(0,-1);return`${t}/ai-access#links`}function ec(e,t,n,r){let o=Ln(e,Ql(t));return{error:Kl,code:Vl,...n?{expiredAt:n}:{},accessLinksUrl:o,hint:r??`Ask the user to create a new access link in Node-RED on the /ai-access dashboard \u2192 Access tokens (${o}).`}}function En(e,t,n,r,o){t.setHeader("WWW-Authenticate",X),t.status(401).json(ec(e,n,r,o))}function tc(e){if(e.length<24||e.startsWith("aia_")&&e.length<40)return"The token may be truncated \u2014 ask the user for the full access-link token."}function fr(e){return e==="get_link"?"Please open Node-RED \u2192 /ai-access \u2192 Access tokens and create a get_link access URL, then send me that /mcp/a/<token> link.":`Please open Node-RED \u2192 /ai-access \u2192 Access tokens and create a kind=${e} token, then send me the Bearer token (not a get_link path URL).`}function rc(e,t){let n=nc;return e==="get_link"?`This URL requires a get_link token. This token is kind=${t} \u2014 ${n} for a get_link access URL (/mcp/a/<token>/\u2026). What to tell the user: ${fr("get_link")}`:e==="rest"?`This REST endpoint requires a rest token. This token is kind=${t} \u2014 ${n}. What to tell the user: ${fr("rest")}`:`This MCP endpoint requires an mcp token. This token is kind=${t} \u2014 ${n}. What to tell the user: ${fr("mcp")}`}function Cn(e,t,n){e.setHeader("WWW-Authenticate",X),e.status(403).json({error:`Access link kind mismatch: expected ${t}, got ${n}`,code:Xl,expectedKind:t,actualKind:n,hint:rc(t,n),tellUser:fr(t)})}function Ut(e,t,n){return(r,o,s)=>{if(!ke(e.settings)){o.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled"});return}let i=Zl(r.headers.authorization);if(i.kind==="basic"){Eo(o,Jl,Yl);return}if(i.kind==="missing"||i.kind==="other"){Eo(o,me,Ie);return}let d=t.validateResult(i.token);if(d.status==="expired"){En(r,o,e.settings.httpAdminRoot,d.expiresAt);return}if(d.status!=="active"){Eo(o,ut,Pn,tc(i.token));return}if(d.link.kind!==n){Cn(o,n,d.link.kind);return}r.accessLink=d.link,s()}}var ut,Kl,me,Jl,X,Ie,Pn,Vl,Yl,Xl,nc,pt=U(()=>{"use strict";ce();ue();ut="Access link is invalid, revoked, or no longer available (it may have expired, or Node-RED may have restarted). Ask the user for a new access link.",Kl="Access link token has expired. Ask the user for a new access link.",me="Access link Bearer token is missing. Ask the user for a new access link and send Authorization: Bearer <token>.",Jl="Authorization must use the Bearer scheme (Authorization: Bearer <access-link-token>), not Basic. Ask the user for a new access link.",X='Bearer realm="node-red-ai-access", charset="UTF-8"',Ie="access_link_missing",Pn="access_link_invalid",Vl="token_expired",Yl="bearer_scheme_required",Xl="wrong_token_kind";a(Zl,"parseAuthorizationHeader");a(Eo,"sendUnauthorized");a(Ql,"accessLinksPagePath");a(ec,"tokenExpiredBody");a(En,"sendTokenExpired");a(tc,"truncatedBearerHint");nc="ask the owner: Node-RED /ai-access \u2192 Access tokens \u2192 create kind=rest/mcp (or get_link)";a(fr,"whatToTellUserWrongTokenKind");a(rc,"wrongTokenKindHint");a(Cn,"sendWrongTokenKind");a(Ut,"requireAccessLink")});var K,W,Se,Ne,hr,xe=U(()=>{"use strict";K=class extends Error{static{a(this,"FlowRevisionConflictError")}code="version_mismatch";statusCode=409;expectedRev;currentRev;context;constructor(t,n,r="updateFlow"){super(`Flow revision conflict in ${r}: expectedRev '${t}' does not match current rev '${n}'. The flows changed since you last read them \u2014 re-read flows, re-apply your change, and retry.`),this.name="FlowRevisionConflictError",this.expectedRev=t,this.currentRev=n,this.context=r}},W=class extends Error{static{a(this,"FlowNotFoundError")}code="flow_not_found";statusCode=404;flowId;constructor(t){super(`Flow '${t}' was not found`),this.name="FlowNotFoundError",this.flowId=t}},Se=class extends Error{static{a(this,"AdapterUnavailableError")}code="adapter_unavailable";statusCode=502;constructor(t){super(t),this.name="AdapterUnavailableError"}},Ne=class extends Error{static{a(this,"PluginMisconfiguredError")}code="plugin_misconfigured";statusCode=502;constructor(t){super(t),this.name="PluginMisconfiguredError"}},hr="Node-RED AI Access could not use the in-process flow runtime and the HTTP Admin API fallback returned Unauthorized. The plugin is misconfigured \u2014 tell the admin to ensure the plugin loads inside Node-RED so RED.runtime.flows is available (do not rely on unauthenticated localhost HTTP when adminAuth is enabled)."});function Fe(e,t){if(!e||!oc.test(e)||e.includes(".."))throw new Error(`Invalid ${t}`);return e}function gr(e){if(!e||!sc.test(e)||e.includes(".."))throw new Error("Invalid proposal id");return e}function Co(e,t){let n=(0,mt.basename)(t);if(!n||n!==t||n.includes(".."))throw new Error("Invalid file name");let r=(0,mt.resolve)(e),o=(0,mt.resolve)(r,n);if(o!==r&&!o.startsWith(r+mt.sep))throw new Error("Path escapes storage directory");return o}function ti(e,t=1e5){if(typeof e=="string")return e.replace(/\0/g,"").slice(0,t)}function ht(e){let t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},n={};for(let o of ic){if(!(o in t))continue;let s=t[o];if(o==="disabled"&&typeof s=="boolean"){n[o]=s;continue}if(o==="env"&&Array.isArray(s)){let d=JSON.stringify(s);d.length<=5e5&&(n[o]=JSON.parse(d));continue}let i=ti(s,1e4);i!==void 0&&(n[o]=i)}for(let o of["nodes","configs"]){let s=t[o];if(!Array.isArray(s))continue;let i=JSON.stringify(s),d=Buffer.byteLength(i,"utf8");if(d>H)throw new Y(`flow.${o}`,d,H);n[o]=JSON.parse(i)}let r=Buffer.byteLength(JSON.stringify(n),"utf8");if(r>H)throw new Y("flow",r,H);return n}function ni(e){let t={};for(let n of ac){if(!(n in e))continue;let r=e[n];if(dc.has(n)){if(typeof r=="number"&&Number.isFinite(r))t[n]=r;else if(typeof r=="string"&&r.trim()!==""){let s=Number(r);Number.isFinite(s)&&(t[n]=s)}continue}let o=ti(r,2e3);o!==void 0&&(t[n]=o)}return t}var mt,oc,sc,ic,ac,dc,yr=U(()=>{"use strict";mt=require("path");G();oc=/^[A-Za-z0-9_.-]+$/,sc=/^prop-[A-Za-z0-9_-]+$/,ic=["id","type","label","disabled","info","env"],ac=["proposalId","flowId","expectedRev","resultRev","reason","code","currentRev","title","displayName","flowLabel","linkId","label","expiresAt","createdAt","ttlSeconds","kind","redactions","restoredRedactions","operation"],dc=new Set(["ttlSeconds","redactions","restoredRedactions"]);a(Fe,"assertSafeSegment");a(gr,"assertSafeProposalId");a(Co,"resolveUnderDir");a(ti,"sanitizeString");a(ht,"sanitizeFlowTab");a(ni,"sanitizeAuditDetails")});function vr(e,t){return`oauth-${(0,br.createHash)("sha256").update(`${e}\0${t}`).digest("hex")}`}function gt(e){return e.kind==="oauth"?vr(e.clientId,e.user):e.linkId}function lc(e){return e==="pending"||e==="approved"||e==="rejected"||e==="failed"||e==="withdrawn"||e==="expired"||e==="superseded"}function uc(e){if(!Array.isArray(e)||e.length===0)return;let t=[];for(let n of e.slice(0,200)){if(!n||typeof n!="object")continue;let{nodeId:r,property:o}=n;typeof r!="string"||typeof o!="string"||t.push({nodeId:r.replace(/\0/g,"").slice(0,200),property:o.replace(/\0/g,"").slice(0,200)})}return t.length>0?t:void 0}function ri(e){if(!Array.isArray(e)||e.length===0)return;let t=[];for(let n of e.slice(0,cc)){if(!n||typeof n!="object"||Array.isArray(n))continue;let r=n;if(typeof r.code!="string"||!r.code||typeof r.error!="string"||!r.error)continue;let o={code:r.code.replace(/\0/g,"").slice(0,80),error:r.error.replace(/\0/g,"").slice(0,500)};typeof r.nodeId=="string"&&r.nodeId&&(o.nodeId=r.nodeId.replace(/\0/g,"").slice(0,200)),typeof r.type=="string"&&r.type&&(o.type=r.type.replace(/\0/g,"").slice(0,200)),typeof r.property=="string"&&r.property&&(o.property=r.property.replace(/\0/g,"").slice(0,200)),typeof r.configType=="string"&&r.configType&&(o.configType=r.configType.replace(/\0/g,"").slice(0,200)),Array.isArray(r.candidates)&&(o.candidates=r.candidates.filter(s=>typeof s=="string"&&s.length>0).slice(0,20).map(s=>s.replace(/\0/g,"").slice(0,200))),t.push(o)}return t.length>0?t:void 0}var br,Xe,oi,se,ne,Bt,In,cc,wr,Ze=U(()=>{"use strict";br=require("crypto"),Xe=require("fs"),oi=require("path");yr();G();a(vr,"oauthOwnerId");a(gt,"proposalOwnerId");se=class extends Error{static{a(this,"ProposalNotFoundError")}code="proposal_not_found";statusCode=404;constructor(t){super(`Proposal '${t}' was not found`),this.name="ProposalNotFoundError"}},ne=class extends Error{static{a(this,"ProposalNotPendingError")}code="proposal_not_pending";statusCode=409;constructor(t,n){super(`Proposal '${t}' is '${n}', not pending`),this.name="ProposalNotPendingError"}},Bt=class extends Error{static{a(this,"ProposalNotOwnedError")}code="proposal_not_owned";statusCode=403;constructor(t){super(`Proposal '${t}' was not created by this access link`),this.name="ProposalNotOwnedError"}},In=class extends Error{static{a(this,"ProposalConcurrentApproveError")}code="proposal_approve_in_progress";statusCode=409;constructor(t){super(`Proposal '${t}' is already being approved`),this.name="ProposalConcurrentApproveError"}};a(lc,"isProposalStatus");cc=50;a(uc,"sanitizeRestoredSecrets");a(ri,"sanitizeProposalWarnings");wr=class{constructor(t,n={}){this.paths=t;this.now=n.now??(()=>Date.now()),this.randomId=n.randomId??(()=>(0,br.randomBytes)(12).toString("hex")),this.maxProposalBytes=n.maxProposalBytes??H}static{a(this,"ProposalStore")}inFlight=new Set;now;randomId;maxProposalBytes;fileFor(t){let n;try{n=gr(t)}catch{throw new se(t)}return Co(this.paths.proposals,`${n}.json`)}toDiskRecord(t){let n=gr(t.id),r=Fe(String(t.expectedRev).slice(0,200),"expectedRev"),o=Fe(String(t.payload.flowId).slice(0,200),"flowId"),s=ht(t.payload.flow),i=t.baseFlow!==void 0?ht(t.baseFlow):void 0,d=t.createdBy.kind==="oauth"?{kind:"oauth",clientId:Fe(String(t.createdBy.clientId).slice(0,200),"clientId"),user:String(t.createdBy.user).replace(/\0/g,"").slice(0,200)}:{kind:"access-link",linkId:Fe(String(t.createdBy.linkId).slice(0,200),"linkId"),...typeof t.createdBy.label=="string"?{label:t.createdBy.label.replace(/\0/g,"").slice(0,200)}:{}},l={id:n,type:"replace_flow",status:lc(t.status)?t.status:"pending",expectedRev:r,payload:{flowId:o,flow:s},createdBy:d,createdAt:String(t.createdAt).slice(0,64),updatedAt:String(t.updatedAt).slice(0,64)};i!==void 0&&(l.baseFlow=i),typeof t.title=="string"&&t.title.length>0&&(l.title=t.title.replace(/\0/g,"").slice(0,80).trim(),l.title||delete l.title);let c=ri(t.warnings);c&&(l.warnings=c);let u=uc(t.restoredSecrets);if(u&&(l.restoredSecrets=u),t.decidedBy)if(t.decidedBy.kind==="access-link"){let p={kind:"access-link",linkId:Fe(String(t.decidedBy.linkId).slice(0,200),"linkId")};typeof t.decidedBy.label=="string"&&(p.label=t.decidedBy.label.replace(/\0/g,"").slice(0,200)),l.decidedBy=p}else{let p={kind:"admin"};typeof t.decidedBy.username=="string"&&(p.username=t.decidedBy.username.replace(/\0/g,"").slice(0,200)),l.decidedBy=p}return typeof t.reason=="string"&&(l.reason=t.reason.replace(/\0/g,"").slice(0,2e3)),typeof t.resultRev=="string"&&(l.resultRev=Fe(t.resultRev.slice(0,200),"resultRev")),l}writeAtomic(t,n){let r=this.fileFor(t),o=this.toDiskRecord(n),s=Je(o,this.maxProposalBytes,{kind:"proposal",pretty:!0});Ot(this.paths.root,r,s)}create(t){let n=new Date(this.now()).toISOString(),r=gr(`prop-${this.randomId()}`),o={id:r,type:"replace_flow",status:"pending",expectedRev:t.expectedRev,payload:{flowId:t.payload.flowId,flow:t.payload.flow},baseFlow:t.baseFlow,title:t.title,createdBy:t.createdBy,createdAt:n,updatedAt:n},s=ri(t.warnings);return s&&(o.warnings=s),t.restoredSecrets?.length&&(o.restoredSecrets=t.restoredSecrets),this.writeAtomic(r,o),this.get(r)}get(t){let n=this.fileFor(t);if(!(0,Xe.existsSync)(n))throw new se(t);return JSON.parse((0,Xe.readFileSync)(n,"utf8"))}list(t){if(!(0,Xe.existsSync)(this.paths.proposals))return[];let n=(0,Xe.readdirSync)(this.paths.proposals).filter(o=>o.endsWith(".json")),r=[];for(let o of n){let s=(0,oi.basename)(o);if(!(s!==o||s.includes("..")))try{let i=JSON.parse((0,Xe.readFileSync)(Co(this.paths.proposals,s),"utf8"));if(t?.linkId&>(i.createdBy)!==t.linkId||t?.status&&i.status!==t.status||t?.flowId&&i.payload.flowId!==t.flowId)continue;r.push(i)}catch{}}return r.sort((o,s)=>o.createdAt<s.createdAt?1:-1)}beginApprove(t){if(this.inFlight.has(t))throw new In(t);let n=this.get(t);if(n.status!=="pending")throw new ne(t,n.status);return this.inFlight.add(t),n}releaseLock(t){this.inFlight.delete(t)}markApproved(t,n,r){let o=this.get(t);return o.status="approved",o.decidedBy=n,o.resultRev=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markRejected(t,n,r){let o=this.get(t);return o.status="rejected",n&&(o.decidedBy=n),o.reason=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markFailed(t,n,r){let o=this.get(t);return o.status="failed",n&&(o.decidedBy=n),o.reason=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markWithdrawn(t,n,r){let o=this.get(t);if(o.status!=="pending")throw new ne(t,o.status);return o.status="withdrawn",o.decidedBy=n,o.reason=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markClosed(t,n,r,o){let s=this.get(t);if(s.status!=="pending")throw new ne(t,s.status);return s.status=n,o&&(s.decidedBy=o),s.reason=r,s.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,s),this.get(t)}}});function si(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`${t} must be a node object`);let n=e;if(typeof n.id!="string"||n.id.length===0)throw new Error(`${t} needs a non-empty string id`);if(typeof n.type!="string"||n.type.length===0)throw new Error(`${t} '${n.id}' needs a non-empty string type`);return n}function kr(e,t,n){let r=e.find(d=>d.id===t);if(!r||r.type!=="tab"&&r.type!=="subflow")throw new W(t);let o={...r,id:t,type:r.type};delete o.nodes,delete o.configs,delete o.subflows,n.label!==void 0&&(o.label=n.label),n.disabled!==void 0&&(o.disabled=n.disabled),n.info!==void 0&&(o.info=n.info),n.env!==void 0&&(o.env=n.env);let s=[];for(let[d,l]of(n.nodes??[]).entries())s.push({...si(l,`flowData.nodes[${d}]`),z:t});for(let[d,l]of(n.configs??[]).entries())s.push({...si(l,`flowData.configs[${d}]`),z:t});return[...e.filter(d=>d.id!==t&&d.z!==t),o,...s]}function Nn(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`Expected ${t} to be an object with rev and flows`);let n=e;if(typeof n.rev!="string"||n.rev.length===0)throw new Error(`Expected ${t}.rev to be a non-empty string`);if(!Array.isArray(n.flows))throw new Error(`Expected ${t}.flows to be an array`);return{rev:n.rev,flows:n.flows}}function qt(e,t){if(Array.isArray(t)){let d=t.find(u=>u&&typeof u=="object"&&u.id===e&&(u.type==="tab"||u.type==="subflow"));if(!d)throw new W(e);let l=t.filter(u=>u&&typeof u=="object"&&u.id!==e&&u.z===e),c={id:e,type:d.type,nodes:l};return typeof d.label=="string"&&(c.label=d.label),typeof d.disabled=="boolean"&&(c.disabled=d.disabled),typeof d.info=="string"&&(c.info=d.info),c}if(!t||typeof t!="object")throw new W(e);let n=t,r=typeof n.id=="string"&&n.id.length>0?n.id:e,o=typeof n.type=="string"&&n.type.length>0?n.type:"tab",s=Array.isArray(n.nodes)?n.nodes:[],i={...n,id:r,type:o,nodes:s};return Array.isArray(n.configs)&&(i.configs=n.configs),i}var Ar=U(()=>{"use strict";xe();a(si,"asFlowNode");a(kr,"replaceFlowTabInFlatFlows");a(Nn,"assertFlowsSnapshot");a(qt,"normalizeFlowTab")});function Fo(e){return e?pc.has(e)?!0:e.startsWith("subflow:"):!1}function Qe(e,t="node.type"){if(e==null||e==="")throw new y(`${t} is required`,"missing_node_type");if(typeof e!="string")throw new y(`${t} must be a non-empty string (got ${typeof e})`,"invalid_node_type",{details:{type:e,got:typeof e}});if(!e.trim())throw new y(`${t} is required`,"missing_node_type");return e}function bc(e,t){if(typeof e!="string"||typeof t!="string")return Number.MAX_SAFE_INTEGER;let n=e.toLowerCase(),r=t.toLowerCase();if(n===r)return 0;let o=n.length,s=r.length;if(o===0)return s;if(s===0)return o;let i=new Array(s+1),d=new Array(s+1);for(let l=0;l<=s;l++)i[l]=l;for(let l=1;l<=o;l++){d[0]=l;let c=n.charAt(l-1);for(let u=1;u<=s;u++){let p=c===r.charAt(u-1)?0:1;d[u]=Math.min((d[u-1]??0)+1,(i[u]??0)+1,(i[u-1]??0)+p)}for(let u=0;u<=s;u++)i[u]=d[u]??0}return i[s]??s}function vc(e,t){let n=e.toLowerCase(),r=t.toLowerCase(),o=Math.min(n.length,r.length),s=0;for(;s<o&&n.charAt(s)===r.charAt(s);)s+=1;return s}function Ac(e){return Math.max(2,Math.floor(e.length/3))}function Oo(e,t,n=5){if(typeof e!="string"||!e)return[];let r=Ac(e);return t.filter(s=>typeof s=="string"&&s!==e&&!kc.has(s)&&!s.startsWith("_")&&!s.startsWith("subflow:")).map(s=>({c:s,dist:bc(e,s),prefix:vc(e,s)})).filter(s=>s.dist<=r).sort((s,i)=>s.dist-i.dist||i.prefix-s.prefix||s.c.localeCompare(i.c)).slice(0,n).map(s=>s.c)}function Sc(e){return e.endsWith("/")?e.slice(0,-1):e}function pi(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,ui.createRequire)(e)}function xc(){try{let t=pi()("@node-red/runtime/lib/settings");return typeof t.available=="function"&&t.available()===!0}catch{return!1}}function fi(e){let t=e.types,n=Array.isArray(t)?t.filter(i=>typeof i=="string"&&i.length>0):[],r="";if(typeof e.module=="string"&&e.module)r=e.module;else if(typeof e.name=="string"&&e.name)r=e.name;else if(typeof e.id=="string"){let i=String(e.id).split("/")[0];r=i&&i.length>0?i:String(e.id)}let o=typeof e.id=="string"&&e.id?e.id:n.length===1&&r?`${r}/${n[0]}`:r;if(!o||n.length===0)return null;let s={id:o,module:r.length>0?r:o,types:n};return typeof e.version=="string"&&(s.version=e.version),typeof e.enabled=="boolean"&&(s.enabled=e.enabled),typeof e.name=="string"&&(s.name=e.name),s}function $o(e=wc,t={}){let n=t.module??"node-red",r=t.version??"4.0.0",o=[...e],s=t.configs??{};return{listNodeSets(){return Promise.resolve([{id:n,module:n,version:r,types:o,enabled:!0,name:n}])},getNodeConfigHtml(i){if(s[i])return Promise.resolve(s[i]??null);let d=i.includes("/")?i.slice(i.lastIndexOf("/")+1):i;return s[d]?Promise.resolve(s[d]??null):Promise.resolve(null)}}}function mi(e={}){if(xc())try{let n=pi()("@node-red/runtime");if(n.nodes&&typeof n.nodes.getNodeList=="function")return new No(n.nodes)}catch{}if(e.baseUrl)return new Io({baseUrl:e.baseUrl,...e.accessToken?{accessToken:e.accessToken}:{},...e.fetch?{fetch:e.fetch}:{}});throw new Error("No usable NodeTypesAdapter: runtime.nodes unavailable and no HTTP baseUrl")}async function Mo(e){let t=await e.listNodeSets(),n=new Map;for(let r of t)for(let o of r.types){if(n.has(o))continue;let s={type:o,module:r.module,setId:r.id};typeof r.enabled=="boolean"&&(s.enabled=r.enabled),r.version&&(s.version=r.version),n.set(o,s)}return{byType:n,allNames:[...n.keys()]}}function Tc(e,t){let n=t.toLowerCase();return[e.type,e.module,e.category??"",e.description??""].join(`
|
|
4
|
-
`).toLowerCase().includes(n)}async function
|
|
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
|
|
6
|
-
`;break;case"r":r="\r";break;case"t":r=" ";break;case"b":r="\b";break;case"f":r="\f";break;case"v":r="\v";break;case"0":r="\0";break;case"\\":r="\\";break;case"'":r="'";break;case'"':r='"';break;case"/":r="/";break;default:r=n;break}return{char:r,next:t+1}}function Ic(e){return e==="_DEFAULT_"?"":e}function Sr(e){let t=e.trimStart();if(t.startsWith("'")||t.startsWith('"')){let r=t[0],o=1,s="";for(;o<t.length;){let i=t[o];if(i==="\\"&&o+1<t.length){let d=Lc(t,o+1);s+=d.char,o=d.next;continue}if(i===r)break;s+=i,o+=1}return Ic(s)}if(t.startsWith("[")){let r=hi(t);if(!r)return;let o=r.slice(1,-1).trim();if(!o)return[];let s=[],i=0;for(;i<o.length;){for(;i<o.length&&/[\s,]/.test(o.charAt(i));)i+=1;if(i>=o.length)break;let d=o.slice(i),l=Sr(d);s.push(l);let c=Lo(d);i+=c>0?c:1}return s}if(t.startsWith("{")){let r=Fn(t);if(!r)return;let o=r.slice(1,-1);if(!o.trim())return{};let s={},i=0;for(;i<o.length;){for(;i<o.length&&/[\s,]/.test(o.charAt(i));)i+=1;if(i>=o.length)break;let d;if(o[i]==="'"||o[i]==='"'){let c=Sr(o.slice(i));d=typeof c=="string"?c:"",i+=Lo(o.slice(i))}else{let c=/^[a-zA-Z_$][\w$]*/.exec(o.slice(i));if(!c)break;d=c[0],i+=d.length}for(;i<o.length&&/\s/.test(o.charAt(i));)i+=1;if(o[i]!==":")break;for(i+=1;i<o.length&&/\s/.test(o.charAt(i));)i+=1;let l=o.slice(i);s[d]=Sr(l),i+=Lo(l)}return s}let n=/^-?\d+(?:\.\d+)?/.exec(t);if(n)return Number(n[0]);if(t.startsWith("true"))return!0;if(t.startsWith("false"))return!1;if(t.startsWith("null"))return null}function Lo(e){let t=e.trimStart(),n=e.length-t.length;if(t.startsWith("'")||t.startsWith('"')){let o=t[0],s=1;for(;s<t.length;){if(t[s]==="\\"&&s+1<t.length){s+=2;continue}if(t[s]===o)return n+s+1;s+=1}return n+t.length}if(t.startsWith("[")){let o=hi(t);return n+(o?.length??1)}if(t.startsWith("{")){let o=Fn(t);return n+(o?.length??1)}let r=/^(?:-?\d+(?:\.\d+)?|true|false|null)/.exec(t);return r?n+r[0].length:n+1}function hi(e){if(!e.startsWith("["))return null;let t=0,n=null,r=!1;for(let o=0;o<e.length;o++){let s=e[o];if(n){if(r){r=!1;continue}if(s==="\\"){r=!0;continue}s===n&&(n=null);continue}if(s==="'"||s==='"'){n=s;continue}if(s==="[")t+=1;else if(s==="]"&&(t-=1,t===0))return e.slice(0,o+1)}return null}function Nc(e){return Sr(e)}function Fc(e,t){for(let n of e)if(n.types.includes(t)){let r=n.id.includes("/")||n.types.length===1?n.id:`${n.module}/${t}`;return{set:n,setId:r}}return null}function Do(e,t){let n=e.toLowerCase();return t.includes(e)?e:t.find(r=>r.toLowerCase()===n)}async function yt(e,t,n={}){if(!t||typeof t!="string")throw new y("Missing node type","missing_node_type");let r=n.includeHelp!==!1;if(Fo(t)){let l={type:t,module:"node-red",category:"config",inputs:0,outputs:0,defaults:null};return r&&(l.help={summary:"Built-in structural Node-RED type (not a palette node)."}),l}let o=await e.listNodeSets(),s=o.flatMap(l=>l.types),i=Fc(o,t);if(!i){let l=Do(t,s);throw l&&l!==t?new y(`Node type '${t}' was not found \u2014 did you mean '${l}'? Types are case-sensitive.`,"node_type_not_found",{statusCode:404,details:{type:t,didYouMean:l,caseSensitive:!0,nearest:[l]}}):new y(`Node type '${t}' was not found`,"node_type_not_found",{statusCode:404,details:{type:t,nearest:Oo(t,s,5)}})}let d={type:t,module:i.set.module,setId:i.setId,inputs:1,outputs:1,defaults:null};if(i.set.version&&(d.version=i.set.version),typeof i.set.enabled=="boolean"&&(d.enabled=i.set.enabled),typeof e.getNodeConfigHtml=="function")try{let l=await e.getNodeConfigHtml(i.setId)??await e.getNodeConfigHtml(`${i.set.module}/${t}`)??await e.getNodeConfigHtml(t);if(l){let c=Pc(l,t);c&&(c.category&&(d.category=c.category),c.color&&(d.color=c.color),d.inputs=c.inputs??d.inputs,d.outputs=c.outputs??d.outputs,c.defaults&&Object.keys(c.defaults).length>0?d.defaults=c.defaults:d.defaults=null,c.requiredConfigNodeTypes&&(d.requiredConfigNodeTypes=c.requiredConfigNodeTypes),r&&c.help&&(d.help=Ec(c.help)))}}catch{}return d}async function gi(e,t){let{allNames:n}=await Mo(e),r=new Set(n),o=new Set,s=a(l=>{let c=Qe(l,"node.type");if(o.has(c)||Fo(c)){o.add(c);return}if(o.add(c),r.has(c))return;let u=Do(c,n);throw u&&u!==c?new y(`Unknown node type '${c}' \u2014 did you mean '${u}'? Types are case-sensitive.`,"unknown_node_type",{details:{type:c,didYouMean:u,caseSensitive:!0,nearest:[u]}}):new y(`Unknown node type '${c}'. Call node-types / palette search; ask the user to install via Manage palette, then recheck.`,"unknown_node_type",{details:{type:c,nearest:Oo(c,n,5)}})},"checkType"),i=a(l=>{if(!l||typeof l!="object"||Array.isArray(l))return;let c=l;typeof c.id=="string"&&c.id.length>0&&s(c.type)},"visitNodeOrConfig");a(l=>{if(!l||typeof l!="object"||Array.isArray(l))return;let c=l;if(c.type!==void 0&&s(c.type),Array.isArray(c.nodes))for(let u of c.nodes)i(u);if(Array.isArray(c.configs))for(let u of c.configs)i(u)},"visitTab")(t)}async function yi(e,t){let n=Qe(t,"node.type");if(Fo(n))return;let{allNames:r}=await Mo(e);if(r.includes(n))return;let o=Do(n,r);throw o&&o!==n?new y(`Unknown node type '${n}' \u2014 did you mean '${o}'? Types are case-sensitive.`,"unknown_node_type",{details:{type:n,didYouMean:o,caseSensitive:!0,nearest:[o]}}):new y(`Unknown node type '${n}'. Call node-types / palette search; ask the user to install via Manage palette, then recheck.`,"unknown_node_type",{details:{type:n,nearest:Oo(n,r,5)}})}async function Oc(e,t){let n=new Headers({Accept:"application/json"});t&&n.set("If-None-Match",t);let r=await fetch(e,{headers:n}),o=r.headers.get("etag")??void 0;if(r.status===304)return{status:304,...o?{etag:o}:{}};if(!r.ok)return{status:r.status,body:null,...o?{etag:o}:{}};let s=await r.json();return{status:r.status,body:s,...o?{etag:o}:{}}}async function $c(e,t){let n=Date.now();if(Te&&n-Te.fetchedAt<yc)return Te.body;let r=Te?.etag,o=await e(t,r);if(o.status===304&&Te)return Te={...Te,fetchedAt:n,...o.etag?{etag:o.etag}:{}},Te.body;if(!o.body||!Array.isArray(o.body.modules)){if(Te)return Te.body;throw new y("Node-RED catalogue unavailable","catalogue_unavailable",{statusCode:502})}return Te={fetchedAt:n,body:o.body,...o.etag?{etag:o.etag}:{}},o.body}async function Tr(e,t,n={}){let r=t.trim();if(!r)throw new y("q query parameter is required","missing_query");let o=n.limit??mc,s=n.catalogueUrl??gc,i=n.catalogueFetcher??Oc,d=await $c(i,s),l=Array.isArray(d.modules)?d.modules:[],c=await e.listNodeSets(),u=new Set(c.flatMap(g=>{let k=[g.module,g.id];return g.id.includes("/")&&k.push(g.id.split("/")[0]??g.module),k.map(T=>T.toLowerCase())})),p=r.toLowerCase(),f=[];for(let g of l){let k=g.id.toLowerCase(),T=(g.keywords??[]).map(Ue=>String(Ue).toLowerCase()),C=(g.description??"").toLowerCase(),L=Mc(p,k,T,C);if(L<=0)continue;let N={module:g.id,installed:u.has(k)};typeof g.description=="string"&&(N.description=g.description),typeof g.version=="string"&&(N.version=g.version),typeof g.updated_at=="string"&&(N.updated=g.updated_at),typeof g.downloads=="number"&&(N.downloads=g.downloads),typeof g.url=="string"&&(N.link=g.url),f.push({item:N,score:L})}f.sort((g,k)=>k.score-g.score||(k.item.downloads??0)-(g.item.downloads??0)||g.item.module.localeCompare(k.item.module));let m=f.length,h=m>o;return{q:r,modules:f.slice(0,o).map(g=>g.item),total:m,truncated:h,hint:hc}}function Mc(e,t,n,r){if(!e)return 0;if(t===e||t===`node-red-contrib-${e}`||t===`node-red-node-${e}`)return 1e4;let o=0;t.startsWith(`node-red-contrib-${e}`)||t.startsWith(`node-red-node-${e}`)?o+=500:t.startsWith(e)?o+=80:t.includes(e)&&(o+=60);for(let s of n)s===e?o+=50:s.startsWith(e)||e.startsWith(s)?o+=35:s.includes(e)&&(o+=25);return r.includes(e)&&(o+=10),o}var ui,pc,fc,mc,hc,gc,yc,wc,kc,Io,ii,No,Te,wt=U(()=>{"use strict";ui=require("module");J();pc=new Set(["tab","subflow","group","junction"]),fc=100,mc=30,hc="Recommend only \u2014 ask the user to install via Manage palette, then recheck node-types. This API never installs. Core Node-RED nodes (module node-red) are not listed in the public catalogue; use node-types for installed core types.",gc="https://catalogue.nodered.org/catalogue.json",yc=3600*1e3,wc=["inject","debug","function","template","comment","change","switch","delay","trigger","link in","link out","link call","mqtt in","mqtt out","http in","http response","http request","websocket in","websocket out","tcp in","tcp out","udp in","udp out","catch","status","complete","join","split","sort","batch","csv","html","json","xml","yaml","file","file in","watch","exec","range","rbe"];a(Fo,"isStructuralNodeType");a(Qe,"requireNodeTypeName");a(bc,"editDistance");a(vc,"sharedPrefixLength");kc=new Set(["unknown","tab","subflow","group"]);a(Ac,"nearestDistanceCutoff");a(Oo,"nearestNodeTypes");a(Sc,"trimTrailingSlash");a(pi,"createPluginRequire");a(xc,"isRuntimeSettingsAvailable");a(fi,"normalizeNodeSet");a($o,"createStaticNodeTypesAdapter");Io=class{static{a(this,"HttpNodeTypesAdapter")}baseUrl;accessToken;fetchImpl;constructor(t){this.baseUrl=Sc(t.baseUrl),this.accessToken=t.accessToken,this.fetchImpl=t.fetch??globalThis.fetch.bind(globalThis)}headers(t){let n=new Headers;return n.set("Accept",t),this.accessToken&&n.set("Authorization",`Bearer ${this.accessToken}`),n}async listNodeSets(){let t=await this.fetchImpl(`${this.baseUrl}/nodes`,{headers:this.headers("application/json")});if(!t.ok)throw new Error(`Admin API GET /nodes failed (${t.status})`);let n=await t.json();if(!Array.isArray(n))throw new Error("Admin API GET /nodes returned non-array JSON");let r=[];for(let o of n){if(!o||typeof o!="object"||Array.isArray(o))continue;let s=fi(o);s&&r.push(s)}return r}async getNodeConfigHtml(t,n){let r=t.split("/").map(i=>encodeURIComponent(i)).join("/"),o=n?`?lang=${encodeURIComponent(n)}`:"",s=await this.fetchImpl(`${this.baseUrl}/nodes/${r}${o}`,{headers:this.headers("text/html")});return s.status===404||!s.ok?null:s.text()}},ii={user:null,req:null},No=class{constructor(t){this.nodes=t}static{a(this,"RedNodeTypesAdapter")}async listNodeSets(){if(typeof this.nodes.getNodeList!="function")return[];let t,n=this.nodes.getNodeList;try{t=await Promise.resolve(n(ii))}catch{try{t=await Promise.resolve(n())}catch{return[]}}if(!Array.isArray(t))return[];let r=[];for(let o of t){if(!o||typeof o!="object"||Array.isArray(o))continue;let s=fi(o);s&&r.push(s)}return r}async getNodeConfigHtml(t,n){if(typeof this.nodes.getNodeConfig!="function")return null;try{let r=await Promise.resolve(this.nodes.getNodeConfig({id:t,lang:n??"en-US",...ii}));if(typeof r=="string"&&r.length>0)return r}catch{}try{let r=await Promise.resolve(this.nodes.getNodeConfig(t,n));return typeof r=="string"&&r.length>0?r:null}catch{return null}}};a(mi,"createNodeTypesAdapter");a(Mo,"flattenInstalledTypes");a(Tc,"matchesQuery");a(xr,"listNodeTypes");a(Rc,"extractHelpScriptBody");a(ai,"removeHtmlBlocks");a(_c,"stripHelpText");a(Pc,"parseRegisterTypeFromHtml");a(di,"parseRegisterTypeAt");a(Ec,"splitHelpSections");a(Fn,"extractBalancedObject");a(li,"matchStringProp");a(ci,"matchNumberProp");a(Cc,"parseDefaultsBlock");a(Lc,"unescapeJsStringEscape");a(Ic,"normalizeDefaultValue");a(Sr,"parseJsLiteral");a(Lo,"measureJsLiteralLength");a(hi,"extractBalancedArray");a(Nc,"parseLooseValue");a(Fc,"findSetForType");a(Do,"findCaseInsensitiveType");a(yt,"getNodeTypeDetail");a(gi,"assertKnownNodeTypes");a(yi,"assertKnownNodeType");Te=null;a(Oc,"defaultCatalogueFetcher");a($c,"loadCatalogue");a(Tr,"searchPalette");a(Mc,"paletteMatchScore")});function qe(e){if(e.length===0)return;let t=e[0];if(!t)return;let n=e.map(s=>{let i={...s};return delete i.statusCode,i});if(e.length>1)throw new y(`${e.length} validation errors`,"validation_failed",{statusCode:400,details:{errors:n,errorCount:e.length}});let r={errors:n};for(let[s,i]of Object.entries(t))s==="code"||s==="error"||s==="errors"||s==="statusCode"||i!==void 0&&(r[s]=i);let o=typeof t.statusCode=="number"?t.statusCode:400;throw new y(t.error,t.code,{statusCode:o,details:r})}function jo(e,t="payload.flow"){let n=new Map;for(let o of e)for(let s of o){if(!s||typeof s!="object"||Array.isArray(s))continue;let i=s.id;typeof i=="string"&&i.length>0&&n.set(i,(n.get(i)??0)+1)}let r=[];for(let[o,s]of n)s>1&&r.push({code:"duplicate_node_id",error:`Node id '${o}' appears ${s} times in ${t}; node ids must be unique`,nodeId:o,count:s});return r}function Dc(e){let t=typeof e.label=="string"&&e.label?` '${e.label}'`:"";return e.type==="tab"?`tab${t||` '${e.id}'`}`:e.type==="subflow"?`subflow${t||` '${e.id}'`}`:typeof e.z=="string"&&e.z?`${e.type} node on '${e.z}'`:`global ${e.type} config node`}function bi(e,t,n){let r=new Map;for(let i of n)!i||typeof i.id!="string"||i.id===t||i.z===t||r.set(i.id,i);let o=[],s=new Set;for(let i of[e.nodes,e.configs])if(Array.isArray(i))for(let d of i){let l=d&&typeof d=="object"?d.id:void 0;if(typeof l!="string"||!l||s.has(l))continue;if(s.add(l),l===t){o.push({code:"duplicate_node_id",error:`Node id '${l}' equals the tab id; node ids must be unique across all flows`,nodeId:l});continue}let c=r.get(l);c&&o.push({code:"duplicate_node_id",error:`Node id '${l}' is already used by ${Dc(c)}; node ids must be unique across all flows`,nodeId:l})}return o}function wi(e){let t=new Map;for(let n of[e.nodes,e.configs])if(Array.isArray(n))for(let r of n){if(!r||typeof r!="object"||Array.isArray(r))continue;let o=r;typeof o.id=="string"&&t.set(o.id,o)}return t}function Hc(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]);for(let r of n)if(!jc.has(r)&&JSON.stringify(e[r])!==JSON.stringify(t[r]))return!0;return!1}function Uc(e,t){let n=wi(t);if(!e)return new Set(n.keys());let r=wi(e),o=new Set;for(let[s,i]of n){let d=r.get(s);(!d||Hc(d,i))&&o.add(s)}return o}function vi(e,t){let n=[],r=e;typeof r.id!="string"||r.id.length===0?n.push({code:"missing_flow_id",error:"payload.flow.id is required and must match payload.flowId"}):r.id!==t&&n.push({code:"invalid_flow_id",error:`payload.flow.id ('${r.id}') must match payload.flowId ('${t}')`}),"nodes"in r?Array.isArray(r.nodes)||n.push({code:"invalid_nodes",error:"payload.flow.nodes must be an array"}):n.push({code:"invalid_nodes",error:"payload.flow.nodes must be an array (use [] to clear the tab intentionally)"}),"configs"in r&&r.configs!==void 0&&!Array.isArray(r.configs)&&n.push({code:"invalid_nodes",error:"payload.flow.configs must be an array"});let o=[];Array.isArray(r.nodes)&&o.push({key:"nodes",list:r.nodes}),Array.isArray(r.configs)&&o.push({key:"configs",list:r.configs}),n.push(...jo(o.map(s=>s.list)));for(let{key:s,list:i}of o)for(let d=0;d<i.length;d+=1){let l=i[d];if(!l||typeof l!="object"||Array.isArray(l)){n.push({code:"invalid_node",error:`payload.flow.${s}[${d}] must be an object`});continue}let c=l;(typeof c.id!="string"||c.id.length===0)&&n.push({code:"missing_node_id",error:`payload.flow.${s}[${d}] needs a non-empty string id`});try{Qe(c.type,"node.type")}catch(u){u instanceof y&&n.push({code:u.code,error:u.message,...typeof c.id=="string"?{nodeId:c.id}:{}})}"wires"in c&&zt(c.wires,n,typeof c.id=="string"?c.id:void 0)}return n}function zt(e,t,n){if(!Array.isArray(e))return t.push({code:"invalid_wires",error:"wires must be an array of output ports (each a list of node ids)",...n?{nodeId:n}:{}}),!1;for(let r of e){if(!Array.isArray(r))return t.push({code:"invalid_wires",error:"wires must be an array of output ports (each a list of node ids)",...n?{nodeId:n}:{}}),!1;for(let o of r)if(typeof o!="string"||o.length===0)return t.push({code:"invalid_wires",error:"wires targets must be non-empty node id strings",...n?{nodeId:n}:{}}),!1}return!0}async function Bc(e,t){if(typeof e.outputs=="number"&&Number.isFinite(e.outputs)&&e.outputs>=0)return Math.trunc(e.outputs);let n=typeof e.type=="string"?e.type:"";if(!(!t||!n))try{return(await yt(t,n,{includeHelp:!1})).outputs}catch{return}}function qc(e,t,n){let r=typeof e.type=="string"?e.type:"node";if(!(n.length<=t))return t===0?{code:"invalid_wires",error:`${r} has 0 outputs; wires must be []`,nodeId:e.id,type:r}:{code:"invalid_wires",error:`${r} has ${t} outputs; wires has ${n.length} port arrays (max ${t})`,nodeId:e.id,type:r}}function zc(e){return Array.isArray(e)&&e.some(t=>typeof t=="string"&&t.length>0)}function Wc(e,t){return t<=0?!1:!("wires"in e)||e.wires===void 0?!0:Array.isArray(e.wires)?!e.wires.some(zc):!1}async function Ho(e,t,n={}){let r=[],o=[],s=new Set(e.map(d=>d.id)),i=n.softWarningNodeIds;for(let d of e){let l=await Bc(d,t),c=typeof d.type=="string"?d.type:"node",u=!i||i.has(d.id);if("wires"in d&&d.wires!==void 0){if(!zt(d.wires,r,d.id))continue;let p=d.wires;if(l!==void 0){let f=qc(d,l,p);f?(f.cause="excess_ports",r.push(f)):u&&p.length<l&&o.push({code:"incomplete_wires",error:`${c} '${d.id}' declares ${l} outputs but wires has only ${p.length} port(s)`,nodeId:d.id,type:c})}for(let f of p)for(let m of f){let h=String(m);s.has(h)||r.push({code:"invalid_wires",error:`Wire target '${h}' on node '${d.id}' was not found in this flow (add the node in the same patch or use an existing id)`,nodeId:d.id,cause:"missing_target",targetId:h})}}u&&l!==void 0&&Wc(d,l)&&o.push({code:"unwired_node",error:`Node '${d.id}' (${c}) has ${l} output(s) but is not wired to anything yet`,nodeId:d.id,type:c})}return{errors:r,warnings:o}}function Uo(e,t){let n=[];for(let r of e)r.z===void 0||r.z===""||typeof r.z=="string"&&r.z!==t&&n.push({code:"z_mismatch",error:`Node '${r.id}' has z='${r.z}' but flow id is '${t}'`,nodeId:r.id});return n}function Gc(e,t){return e.filter(n=>n.type===t).map(n=>n.id)}async function Bo(e,t,n){let r=[],o=[...t];for(let s of e){let i;try{i=Qe(s.type,"node.type")}catch{continue}let d;try{d=await yt(n,i,{includeHelp:!1})}catch{continue}let l=d.defaults;if(!l||typeof l!="object")continue;let c=new Set(d.requiredConfigNodeTypes??[]);for(let[u,p]of Object.entries(l)){if(!p||typeof p!="object"||Array.isArray(p))continue;let f=p.type;if(typeof f!="string"||!f)continue;let h=p.required===!0||c.has(f),g=s[u],k=Gc(o,f);if(typeof g!="string"||g.length===0){h&&r.push({code:"missing_config_node",error:`Node '${s.id}' (${i}) has no ${f} on '${u}' yet \u2014 configure before deploy if needed`,nodeId:s.id,type:i,property:u,configType:f,candidates:k});continue}o.find(C=>C.id===g)?.type!==f&&r.push({code:"missing_config_node",error:`Node '${s.id}' property '${u}' does not resolve to a ${f} config yet`,nodeId:s.id,type:i,property:u,configType:f,candidates:k})}}return r}async function ki(e,t={}){let n=t.flowId??(typeof e.id=="string"?e.id:""),r=Array.isArray(e.nodes)?e.nodes:[],o=Array.isArray(e.configs)?e.configs:[],s=[],i=[];n&&s.push(...Uo(r,n));let d=t.baseFlow?Uc(t.baseFlow,e):void 0,l=await Ho(r,t.nodeTypes,{...d?{softWarningNodeIds:d}:{}});if(s.push(...l.errors),i.push(...l.warnings),t.nodeTypes){let c=await Bo(r,o,t.nodeTypes);d?i.push(...c.filter(u=>!u.nodeId||d.has(u.nodeId))):i.push(...c)}return{errors:s,warnings:i}}var jc,qo=U(()=>{"use strict";J();wt();a(qe,"throwValidationIssues");a(jo,"collectDuplicateNodeIdIssues");a(Dc,"describeLiveNode");a(bi,"collectCrossFlowDuplicateIssues");jc=new Set(["x","y","w","h","g","id","z"]);a(wi,"collectNodesMap");a(Hc,"nodeContentChanged");a(Uc,"changedNodeIds");a(vi,"collectFullFlowShapeIssues");a(zt,"assertValidWiresShape");a(Bc,"resolveNodeOutputCount");a(qc,"wireCountIssue");a(zc,"portHasTargets");a(Wc,"nodeIsUnwired");a(Ho,"collectWireValidation");a(Uo,"collectZMismatchIssues");a(Gc,"configCandidates");a(Bo,"collectConfigWarnings");a(ki,"validateFlowTab")});function et(e,t="node"){return typeof e=="string"&&e.trim()?e.trim():t}function Rr(e){let t=new Map;for(let n of e)n.type==="tab"&&typeof n.id=="string"&&typeof n.label=="string"&&n.label.trim()&&t.set(n.id,n.label.trim());return t}function Si(e,t){e[Ai]=t}function Vc(e){if(!e)return;let t=e[Ai];return t instanceof Map?t:void 0}function Yc(e){return typeof e!="string"||!e.trim()?new Set:new Set(e.split(",").map(t=>t.trim()).filter(Boolean))}function Xc(e){if(e===!0||e===1)return!0;if(typeof e=="string"){let t=e.trim().toLowerCase();return t==="1"||t==="true"||t==="yes"}return!1}function ie(e){let t=Yc(e.include);return Xc(e.include_payload)&&(t.add("payload"),t.add("baseFlow")),t}function q(e,t){let n={req:e};t!==void 0&&(n.httpAdminRoot=t);let r=Vc(e);return r&&(n.liveFlowLabels=r),n}function Wt(e){return typeof e.name=="string"&&e.name.trim()?e.name.trim():typeof e.label=="string"&&e.label.trim()?e.label.trim():""}function On(e){return`"${e.replace(/"/g,"'")}"`}function Gt(e){let t=new Map;for(let n of e.nodes??[])t.set(n.id,n);for(let n of e.configs??[])t.set(n.id,n);return t}function Zc(e,t){let n=new Set(["x","y","w","h","g","id","z"]),r=new Set([...Object.keys(e),...Object.keys(t)]);for(let o of r)if(!n.has(o)&&JSON.stringify(e[o])!==JSON.stringify(t[o]))return!0;return!1}function Qc(e,t){let n=Wt(e),r=Wt(t);if(!n||!r||n===r)return!1;let o=new Set(["x","y","w","h","g","id","z","name","label"]),s=new Set([...Object.keys(e),...Object.keys(t)]);for(let i of s)if(!o.has(i)&&JSON.stringify(e[i])!==JSON.stringify(t[i]))return!1;return!0}function zo(e,t){let n=[],r=[],o=[];for(let[s,i]of t)e.has(s)||n.push(i);for(let[s,i]of e)t.has(s)||r.push(i);for(let[s,i]of t){let d=e.get(s);d&&Zc(d,i)&&o.push({before:d,after:i})}return{added:n,removed:r,modified:o}}function _r(e){return e===1?"node":"nodes"}function eu(e){return e.length===1&&e[0]?`adds 1 ${et(e[0].type)}`:`adds ${e.length} ${_r(e.length)}`}function tu(e){return`deletes ${e} ${_r(e)}`}function nu(e){return`edits ${e} ${_r(e)}`}function ru(e){let t=[];e.removed>0&&t.push(tu(e.removed));let n=e.added??[],r=e.addedCount??n.length;r>0&&t.push(n.length===r?eu(n):`adds ${r} ${_r(r)}`),(e.modified??0)>0&&t.push(nu(e.modified??0));let o=t.length?t.join(", "):"replaces nodes",s=e.flowLabel?.trim();return s?`Replace ${s}: ${o}`:`Replace flow: ${o}`}function ou(e){let{added:t,removed:n,modified:r}=e;if(t.length+n.length+r.length!==1)return;if(t[0]){let u=t[0],p=et(u.type),f=Wt(u);return f?`Add ${p} node ${On(f)}`:`Add ${p} node`}if(n[0]){let u=n[0],p=et(u.type),f=Wt(u);return f?`Remove ${p} node ${On(f)}`:`Remove ${p} node`}let s=r[0];if(!s)return;let i=et(s.after.type,et(s.before.type)),d=Wt(s.before),l=Wt(s.after);if(Qc(s.before,s.after)&&d&&l)return`Rename ${i} node ${On(d)} \u2192 ${On(l)}`;let c=l||d;return c?`Edit ${i} ${On(c)}`:`Edit ${i}`}function Wo(e,t){return typeof e?.label=="string"&&e.label.trim()?e.label.trim():typeof t?.label=="string"&&t.label.trim()?t.label.trim():""}function Pr(e,t){if(typeof e.flowLabel=="string"&&e.flowLabel.trim())return e.flowLabel.trim();let n=Wo(e.payload?.flow,e.baseFlow);if(n)return n;let r=e.payload?.flowId;return r&&t?.has(r)?t.get(r)??"":""}function xi(e,t){let n=t.trim(),r=e.trim();if(!n||!r||r.includes(n))return r;let o=r.lastIndexOf(" in ");if(o>=0){let s=r.slice(o+4);if(s.length>0&&n.startsWith(s))return r}return`${r} in ${n}`}function Go(e,t){let n=zo(Gt(e??{}),Gt(t)),r=n.added.length+n.removed.length+n.modified.length,o=Wo(t,e);if(r===0)return o?`Update ${o}`:"Update flow";if(n.removed.length>0)return ru({...o?{flowLabel:o}:{},removed:n.removed.length,added:n.added,modified:n.modified.length});if(r>Jc)return o?`${r} changes in ${o}`:`${r} changes`;let s=ou(n)??(o?`Update ${o}`:"Update flow");return xi(s,o)}function su(e,t){let n=(e??"").trim();return!!(!n||n.includes("[object Object]")||/^Update(\s|$)/i.test(n)||t>0&&!/^Replace\b/i.test(n))}function Ko(e,t){return`${e} \xB7 ${au(t)}`}function iu(e){let t=e>=0?"+":"-",n=Math.abs(e),r=String(Math.floor(n/60)).padStart(2,"0"),o=String(n%60).padStart(2,"0");return`${t}${r}:${o}`}function au(e,t=new Date(e)){let n=Number.isNaN(t.getTime())?new Date(e):t;if(Number.isNaN(n.getTime()))return e.slice(0,16);let r=n.getDate(),s=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][n.getMonth()]??"Jan",i=String(n.getHours()).padStart(2,"0"),d=String(n.getMinutes()).padStart(2,"0"),l=iu(-n.getTimezoneOffset());return`${r} ${s} ${i}:${d} ${l}`}function Er(e){if(typeof e!="string")return;let t=e.replace(/\0/g,"").replace(/[\u0001-\u001F\u007F]/g," ").replace(/\s+/g," ").trim().slice(0,Kc).trim();return t.length>0?t:void 0}function du(e,t){let n=pe(t);return`${n==="/"?"/ai-access":`${n}/ai-access`}#proposal=${encodeURIComponent(e)}`}function Jo(e,t={}){let n=Pr(e,t.liveFlowLabels),r=Er(e.title),o=Go(e.baseFlow,n&&!Wo(e.payload.flow,e.baseFlow)?{...e.payload.flow,label:n}:e.payload.flow),s=zo(Gt(e.baseFlow??{}),Gt(e.payload.flow)),i=su(r,s.removed.length)?o:r??o;return xi(i,n)}function lu(e){return typeof e.storedTitle=="string"&&e.storedTitle?e.storedTitle:Er(e.title)}function ae(e,t={}){let n=Jo(e,t),r=Pr(e,t.liveFlowLabels),o=lu(e)??n,s=du(e.id,t.httpAdminRoot),i=t.req?Ln(t.req,s):s;return{...e,title:n,storedTitle:o,flowLabel:r,displayName:Ko(n,e.createdAt),reviewUrl:i,reviewPath:s}}function cu(e){let t=zo(Gt(e.baseFlow??{}),Gt(e.payload.flow)),n=[];return t.added.length>0&&n.push(t.added.length===1?"Adds 1 node":`Adds ${t.added.length} nodes`),t.removed.length>0&&n.push(t.removed.length===1?"Deletes 1 node":`Deletes ${t.removed.length} nodes`),t.modified.length>0&&n.push(t.modified.length===1?"Edits 1 node":`Edits ${t.modified.length} nodes`),n.length===0?"No node changes":n.map((r,o)=>o===0?r:r.charAt(0).toLowerCase()+r.slice(1)).join(", ")}function uu(e){if(e.kind==="oauth")return{kind:"oauth",clientId:e.clientId,user:e.user};let t={kind:e.kind,linkId:e.linkId};return typeof e.label=="string"&&e.label.length>0&&(t.label=e.label),t}function he(e,t={}){let n=ae(e,t),r=t.include??new Set,o={id:n.id,status:n.status,type:n.type,title:n.title,storedTitle:n.storedTitle,displayName:n.displayName,flowLabel:n.flowLabel,reviewUrl:n.reviewUrl,reviewPath:n.reviewPath,createdAt:n.createdAt,updatedAt:n.updatedAt,expectedRev:n.expectedRev,createdBy:uu(n.createdBy),changeSummary:cu(e)};return r.has("payload")&&(o.payload=n.payload),r.has("baseFlow")&&n.baseFlow!==void 0&&(o.baseFlow=n.baseFlow),n.warnings&&n.warnings.length>0&&(o.warnings=n.warnings),typeof e.payload.flowId=="string"&&(o.flowId=e.payload.flowId),e.restoredSecrets&&e.restoredSecrets.length>0&&(o.restoredSecrets=e.restoredSecrets),n.baseRevStale!==void 0&&(o.baseRevStale=n.baseRevStale),n.decidedBy!==void 0&&(o.decidedBy=n.decidedBy),n.reason!==void 0&&(o.reason=n.reason),n.resultRev!==void 0&&(o.resultRev=n.resultRev),o}function Ti(e,t={}){return e.map(n=>he(n,t))}var Kc,Jc,Ai,Re=U(()=>{"use strict";ce();Le();Kc=80,Jc=1;a(et,"nodeTypeText");Ai=Symbol.for("ai-access.liveFlowLabels");a(Rr,"liveFlowLabelsFromFlows");a(Si,"setRequestFlowLabels");a(Vc,"getRequestFlowLabels");a(Yc,"parseIncludeQuery");a(Xc,"isTruthyFlag");a(ie,"resolveIncludeFromQuery");a(q,"presentOptionsFor");a(Wt,"nodeLabel");a(On,"quoteName");a(Gt,"collectNodes");a(Zc,"nodeContentChanged");a(Qc,"isRenameOnly");a(zo,"classifyFlowChanges");a(_r,"pluralNodes");a(eu,"describeAddedClause");a(tu,"describeRemovedClause");a(nu,"describeModifiedClause");a(ru,"buildReplaceFlowTitle");a(ou,"describeOneChange");a(Wo,"flowLabelHint");a(Pr,"resolveFlowLabel");a(xi,"ensureTitleIncludesFlowName");a(Go,"autoGenerateProposalTitle");a(su,"shouldRefreshDisplayTitle");a(Ko,"buildProposalDisplayName");a(iu,"formatUtcOffset");a(au,"formatProposalSubmittedAt");a(Er,"sanitizeProposalTitle");a(du,"buildProposalReviewPath");a(Jo,"resolveProposalTitle");a(lu,"storedTitleOf");a(ae,"presentProposal");a(cu,"buildChangeSummary");a(uu,"presentCreatedByForApi");a(he,"presentProposalForApi");a(Ti,"presentProposalsForApi")});function Ri(e){let t=new Map;for(let n of e.nodes??[])t.set(n.id,n);for(let n of e.configs??[])t.set(n.id,n);return t}function fu(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]),r=[];for(let o of n)pu.has(o)||JSON.stringify(e[o])!==JSON.stringify(t[o])&&r.push(o);return r.sort()}function _i(e){let t=Ri(e.baseFlow??{}),n=Ri(e.payload.flow),r=new Map;for(let[o,s]of n){let i=t.get(o);if(!i){r.set(o,new Set(["*"]));continue}let d=fu(i,s);d.length>0&&r.set(o,new Set(d))}for(let[o]of t)n.has(o)||r.set(o,new Set(["*"]));return r}function mu(e,t){let n=[],r=[];for(let[o,s]of e){let i=t.get(o);if(!i)continue;n.push(o);let d=[];for(let c of s){if(c==="*"){d.push(...[...i].filter(u=>u!=="*")),i.has("*")&&d.push("*");continue}(i.has(c)||i.has("*"))&&d.push(c)}let l=[...new Set(d)].sort();l.length>0&&r.push({nodeId:o,fields:l})}return n.sort(),{nodeIds:n,fields:r}}function V(e,t,n={}){let r=0;for(let o of e.list())if(o.status==="pending"&&!(n.flowId&&o.payload.flowId!==n.flowId)&&o.expectedRev!==t)try{e.markClosed(o.id,"expired",hu),r+=1}catch{}return r}function Pi(e,t,n,r){let o=`Superseded by approved proposal ${n} (resultRev ${r}). Re-read the flow and resubmit if still needed.`,s=0;for(let i of e.list())if(!(i.id===n||i.status!=="pending"||i.payload.flowId!==t))try{e.markClosed(i.id,"superseded",o),s+=1}catch{}return s}function Oe(e,t,n){let r=_i(t),o=[];for(let s of e.list()){if(s.id===t.id||s.status!=="pending"||s.payload.flowId!==t.payload.flowId)continue;let i=Jo(s),d=n?gt(s.createdBy)===n:gt(s.createdBy)===gt(t.createdBy),l=mu(r,_i(s)),c=s.expectedRev===t.expectedRev,u=l.nodeIds.length>0;if(!c&&!u)continue;let p={id:s.id,title:i,displayName:Ko(i,s.createdAt),mine:d};l.nodeIds.length>0&&(p.overlappingNodeIds=l.nodeIds),l.fields.length>0&&(p.overlappingFields=l.fields),o.push(p)}return{conflictingProposals:o}}function $e(e){let t={};return e.conflictingProposals.length>0&&(t.conflictingProposals=e.conflictingProposals,t.warning="Other pending proposals touch the same flow \u2014 approve order may conflict (like a git merge)."),t}function Z(e){return e>0?{autoClosedCount:e}:{}}var pu,hu,tt=U(()=>{"use strict";Ze();Re();pu=new Set(["x","y","w","h","g","id","z"]);a(Ri,"collectNodes");a(fu,"changedFieldKeys");a(_i,"computeProposalTouches");a(mu,"overlapBetween");hu="Proposal expired: the live flow revision moved past expectedRev. Re-read the flow and resubmit.";a(V,"expireStalePendingProposals");a(Pi,"supersedePendingOnFlow");a(Oe,"findConflictingProposals");a($e,"conflictWarningFields");a(Z,"autoClosedFields")});function Nr(e){let t=e.join("\0"),n=Cr.get(t);if(n)return n;let r=[...new Set(e.map(d=>sr(d).join("")).filter(Boolean))],o=new Map,s={patterns:[...e],matches(d){if(typeof d!="string"||d.length===0||r.length===0)return!1;let l=o.get(d);if(l!==void 0)return l;let c=i(d);return o.size>4096&&o.clear(),o.set(d,c),c}};function i(d){{let l=sr(d),c=l[l.length-1];if(!c||gu.has(c))return!1;for(let u of r){let p=`${u}S`;for(let f=0;f<l.length;f++){let m="";for(let g=f;g<l.length;g++){if(m+=l[g],m===u||m===p)return!0;if(m.length>p.length)break}let h=l[f]??"";if(u.length>=6&&(h.endsWith(u)||h.endsWith(p)))return!0}}return!1}}return a(i,"matchUncached"),Cr.size>32&&Cr.clear(),Cr.set(t,s),s}function Kt(){return qs()?.getEffective()?.secretNamePatterns??ct}function Dn(e){let t=e.trim();return!(!t||t===te||yu.has(t.toLowerCase())||/^\$\{[A-Za-z0-9_]+\}$/.test(t))}function bu(e){let t=Li.get(e);return t||(t=vu(e),Li.set(e,t)),t}function vu(e){return[{hints:["Buffer.from","btoa"],re:/\b(?:Buffer\.from|btoa)\([ \t]{0,8}(?<q>["'`])[^"'`:\s]{1,256}:(?<s>[^"'`\s]{1,1024})\k<q>/dg,accept:a(t=>!t.includes("${"),"accept")},{hints:["PRIVATE KEY-----"],re:/-----BEGIN ((?:[A-Z0-9]{1,16} ){0,4})PRIVATE KEY-----(?<s>[^-]{1,16384})-----END \1PRIVATE KEY-----/dg},{hints:["AKIA","ASIA","ABIA","ACCA"],re:/(?<![A-Za-z0-9])(?<s>(?:AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{16})(?![A-Za-z0-9])/dg},{hints:["ghp_","gho_","ghu_","ghs_","ghr_"],re:/(?<![A-Za-z0-9_])(?<s>gh[pousr]_[A-Za-z0-9]{36,255})(?![A-Za-z0-9_])/dg},{hints:["github_pat_"],re:/(?<![A-Za-z0-9_])(?<s>github_pat_[A-Za-z0-9_]{22,255})(?![A-Za-z0-9_])/dg},{hints:["xox"],re:/(?<![A-Za-z0-9_-])(?<s>xox[abposr]-[A-Za-z0-9-]{10,512})/dg},{hints:["hooks.slack.com"],re:/hooks\.slack\.com\/services\/(?<s>[A-Za-z0-9_/]{16,512})/dg},{hints:["sk-"],re:/(?<![A-Za-z0-9_-])(?<s>sk-[A-Za-z0-9_-]{20,512})/dg},{hints:["k_live_","k_test_"],re:/(?<![A-Za-z0-9_])(?<s>[sr]k_(?:live|test)_[A-Za-z0-9]{16,512})/dg},{hints:["AIza"],re:/(?<![A-Za-z0-9_-])(?<s>AIza[0-9A-Za-z_-]{35})(?![A-Za-z0-9_-])/dg},{hints:["eyJ"],re:/(?<![A-Za-z0-9_-])(?<s>eyJ[A-Za-z0-9_-]{5,8192}\.[A-Za-z0-9_-]{5,8192}\.[A-Za-z0-9_-]{5,8192})/dg},{hints:["earer"],re:/\b[Bb]earer[ \t]{1,8}(?<s>(?=[A-Za-z0-9\-._~+/]{0,4096}[0-9\-._~+/])[A-Za-z0-9\-._~+/]{16,4096}={0,3})/dg},{hints:["asic"],re:/\b[Bb]asic[ \t]{1,8}(?<s>(?=[A-Za-z0-9+/]{0,4096}[0-9+/])[A-Za-z0-9+/]{8,4096}={0,2})(?![A-Za-z0-9+/])/dg},{hints:["@"],re:/\b[A-Za-z][A-Za-z0-9+.-]{1,20}:\/\/[^\s:@/'"`<>]{1,256}:(?<s>[^\s@/'"`<>]{1,1024})@/dg,accept:a(t=>!/^(?:\$\{|\{\{|<|%)/.test(t)&&t!==te,"accept")},{hints:["?","&"],re:/[?&](?<k>[A-Za-z_][\w.-]{0,63})=(?<s>[^&#\s"'`<>]{1,2048})/dg,accept:a((t,n)=>e.matches(n.groups?.k??"")&&Dn(Tu(t)),"accept")},{hints:[":","="],re:/(?<![\w$-])(?<q>["'`]?)(?<k>[A-Za-z_$][\w$-]{0,63})\k<q>\]?[ \t]{0,8}(?::|=(?![=>]))[ \t]{0,8}(?<vq>["'`])(?<s>(?:\\.|(?!\k<vq>)[^\\\r\n]){1,1024})\k<vq>/dg,accept:a((t,n)=>{let r=n.groups?.k??"";return!e.matches(r)||n.groups?.vq==="`"&&t.includes("${")||t.length<3||/\s/.test(t)?!1:Dn(t)},"accept")}]}function Au(e){let t=new Map;for(let r of e)t.set(r,(t.get(r)??0)+1);let n=0;for(let r of t.values()){let o=r/e.length;n-=o*Math.log2(o)}return n}function Su(e){return/[^A-Za-z]/.test(e)||e.length>=16?!0:[/[a-z]/,/[A-Z]/,/[0-9]/,/[^A-Za-z0-9]/].filter(n=>n.test(e)).length>=3||Au(e)>=3}function xu(e){let t=e.patterns.filter(r=>ku.includes(r.toUpperCase())),n=Nr(t);return{hints:[":","="],re:/(?<![\w$.-])(?<k>[A-Za-z_][\w-]{0,63})[ \t]{0,8}(?::(?!\/\/)|=)[ \t]{0,8}(?<s>[^\s;,'"`&<>(){}[\]]{3,1024})/dg,accept:a((r,o)=>e.matches(o.groups?.k??"")&&Dn(r)&&!r.startsWith("$")&&!r.startsWith("{{")&&(n.matches(o.groups?.k??"")||Su(r))&&!/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(r)&&o.input.charAt(o.indices?.groups?.s?.[1]??0)!=="(","accept")}}function Tu(e){try{return decodeURIComponent(e)}catch{return e}}function Ru(e,t,n){let r=e.indexOf("=",t);if(r<=t||r>=n||r-t>48)return;let o=e.slice(t,r),s=Math.max(o.lastIndexOf('"'),o.lastIndexOf("'"),o.lastIndexOf("`")),i=o.slice(s+1).trim();if(!/^[A-Za-z][\w -]{0,40}$/.test(i))return;let d=r+1;for(;d<n&&(e[d]===" "||e[d]===" ");)d++;let l=d;for(;l<n&&!"\"'`\r".includes(e[l]??"");)l++;for(;l>d&&(e[l-1]===" "||e[l-1]===" ");)l--;return{k:i,vs:d,ve:l}}function _u(e,t){if(!e.includes(";")||!e.includes("="))return[];let n=[],r=0;for(;r<=e.length;){let o=e.indexOf(`
|
|
7
|
-
`,r);o<0&&(o=e.length);let s=[],i=r,d=!1;for(;i<o;){let l=e.indexOf(";",i);l<0||l>o?l=o:d=!0;let c=
|
|
3
|
+
`,o=Buffer.from(r,"utf8");if(o.byteLength>t)throw new le(n.kind,o.byteLength,t);return{bytes:o}}function Yi(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 cn(e,t,n){let r=Yi(t,e),o=`${r}.${process.pid}.tmp`;(0,st.writeFileSync)(o,n.bytes,{mode:Or}),(0,st.renameSync)(o,r),(0,st.chmodSync)(r,Or)}function Xi(e,t,n){let r=Yi(t,e);(0,st.appendFileSync)(r,n.bytes,{mode:Or});try{(0,st.chmodSync)(r,Or)}catch{}}var st,Wn,hs,Or,K,Vi,le,se=q(()=>{"use strict";st=require("fs"),Wn=require("path"),hs=448,Or=384,K=15e5,Vi=8192,le=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(ft,"encodeTrustedJson");a(Yi,"assertUnderAiAccess");a(cn,"writeTrustedAtomic");a(Xi,"appendTrusted")});function Kn(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 su(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 Qi(e){return(t,n,r)=>{if(!tu.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=Kn(t.get("origin")),d=i===void 0?void 0:e().find(c=>c===i),l=d!==void 0;if(su(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",ou),t.method==="OPTIONS"&&t.get("access-control-request-method")){n.setHeader("Access-Control-Allow-Methods",nu),n.setHeader("Access-Control-Allow-Headers",ru),n.setHeader("Access-Control-Max-Age",String(600)),n.status(204).end();return}s=!1,r()}}function ea(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 tu,nu,ru,ou,Dr=q(()=>{"use strict";tu=/^\/mcp(?:\/?$|\/(?:a(?:\/|$)|api(?:\/|$)|rpc\/?$))/,nu="GET, POST, DELETE, OPTIONS",ru="Authorization, Content-Type, Accept, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID",ou="Mcp-Session-Id, WWW-Authenticate, Retry-After, RateLimit, RateLimit-Policy, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset";a(Kn,"canonicalOrigin");a(su,"appendVary");a(Qi,"aiCorsMiddleware");a(ea,"installAiCorsMiddleware")});function jr(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 Vn(e){if(!Array.isArray(e))return;let t=[];for(let n of e){if(typeof n!="string")continue;let r=n.trim().toUpperCase();if(!(!Hr.test(r)||t.includes(r))&&(t.push(r),t.length>=Jn))break}return t}var J,Ft,Hr,Jn,un=q(()=>{"use strict";J="[REDACTED]",Ft=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"]),Hr=/^[A-Za-z0-9_-]{2,40}$/,Jn=100;a(jr,"tokenizeName");a(Vn,"normalizeSecretNamePatterns")});function au(e){return e==="nodes"||e==="flows"||e==="full"}function Ur(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)),typeof n.proposalTtlDays=="number"&&Number.isInteger(n.proposalTtlDays)&&n.proposalTtlDays>=1&&n.proposalTtlDays<=365&&(r.proposalTtlDays=n.proposalTtlDays),au(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=hn(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=Kn(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=Vn(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 du(e,t){if(!(0,Xn.existsSync)(e))return{};try{let n=(0,Xn.readFileSync)(e,"utf8");return Ur(JSON.parse(n),t)}catch(n){return t?.(`AI Access: config.json is corrupt or unreadable; defaults are used (${String(n)})`),{}}}function Br(e,t){let n={},r={...Yn};for(let s of Object.keys(Yn).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]??Yn[s]});let o={...r,sources:n};return e.oauth!==void 0||t.oauth!==void 0?(o.oauth={...pn,...t.oauth??{},...e.oauth??{}},n.oauth=e.oauth!==void 0?"settings.js":"disk"):(o.oauth={...pn},n.oauth="default"),o}function ys(e,t){let n=Ur(e??{},t);for(let o of Object.keys(Yn).filter(s=>s!=="oauth"))e&&Object.hasOwn(e,o)&&n[o]===void 0&&Object.assign(n,{[o]:Yn[o]});let r=e?.oauth;if(r&&typeof r=="object"&&!Array.isArray(r)){let o=n.oauth??={};for(let s of Object.keys(pn))Object.hasOwn(r,s)&&o[s]===void 0&&(t?.(`AI Access: settings.js aiAccess.oauth.${s} is invalid; using the default (${String(pn[s])})`),Object.assign(o,{[s]:pn[s]}))}return n}var Xn,gs,iu,pn,Yn,fn,qr=q(()=>{"use strict";Xn=require("fs"),gs=require("path");ve();Dr();un();se();iu=16384,pn={enabled:!1,accessTokenTtlSec:3600,refreshIdleTtlSec:2592e3,refreshMaxLifetimeSec:7776e3,dcrMaxClients:5},Yn={enabled:!0,allowWithoutAdminAuth:!1,defaultLinkTtlSeconds:14400,proposalTtlDays:7,maxActiveLinks:10,defaultDeploymentType:"nodes",publicBaseUrl:"",corsAllowedOrigins:[],rateLimitWindowMs:6e4,rateLimitMaxPerIp:600,rateLimitMaxPerToken:300,secretNamePatterns:[...Ft],oauth:{...pn}};a(au,"isDeploymentType");a(Ur,"sanitizePartial");a(du,"readDiskFile");a(Br,"mergeAiAccessSettings");a(ys,"settingsJsPartial");fn=class{constructor(t,n,r){this.paths=t;this.filePath=(0,gs.join)(t.root,"config.json"),this.settingsJs=ys(n,r),this.disk=du((0,Xn.existsSync)(this.filePath)?this.filePath:(0,gs.join)(t.root,"settings.json"),r),this.cached=Br(this.settingsJs,this.disk)}static{a(this,"SettingsStore")}filePath;disk;settingsJs;cached;getEffective(){return this.cached}getDisk(){return{...this.disk}}update(t){let n=Ur(t);for(let r of Object.keys(this.settingsJs))delete n[r];return this.persist(n),this.disk=n,this.cached=Br(this.settingsJs,this.disk),this.cached}patch(t,n=[]){let r=Ur(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=ft(t,iu,{kind:"settings",pretty:!0});cn(this.paths.root,this.filePath,n)}}});function Wr(e){zr=e}function ta(){return zr}function oe(e){return e.adminAuth!=null&&e.adminAuth!==!1}function Ee(e){return zr?zr.getEffective():Br(ys(e.aiAccess),{})}function Ce(e){let t=Ee(e);return t.enabled?oe(e)?!0:t.allowWithoutAdminAuth:!1}var zr,ke=q(()=>{"use strict";qr();zr=null;a(Wr,"bindSettingsStore");a(ta,"getSettingsStore");a(oe,"isAdminAuthEnabled");a(Ee,"resolveAiAccessSettings");a(Ce,"areAiFeaturesEnabled")});function Ae(e){let t=`/${e??""}`.replace(/\/{2,}/g,"/");for(;t.length>1&&t.endsWith("/");)t=t.slice(0,-1);return t}function Se(e){let t=Ae(e);return t==="/"?"/mcp/api/v1":`${t}/mcp/api/v1`}function Zn(e){let t=Ae(e);return t==="/"?"/ai-access":`${t}/ai-access`}function ht(e){let t=Ae(e);return t==="/"?"/mcp/rpc":`${t}/mcp/rpc`}function lu(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 mt(e){if(e.auth)return e.auth.scopes.includes("propose")?"propose":e.auth.scopes.includes("read")?"read":null;let t=lu(e);return t.includes("*")?"*":t.includes("read")?"read":null}function cu(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 na(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 Ie(e,t){let n="read";return(r,o,s)=>{if(!oe(e.settings)){s();return}let i=t==="api"?na(o):null;e.auth.needsPermission(n)(r,o,d=>{if(i?.(),d){s(d);return}if(!r.user){if(t==="page"){o.redirect(Ae(e.settings.httpAdminRoot));return}o.status(401).json({error:"Authentication required",code:"auth_required"});return}if(!mt(r)){o.status(401).json({error:"Insufficient permissions",code:"insufficient_permissions"});return}s()})}}function Gr(e,t){let n=oe(e.settings),r=Ee(e.settings),o=Ce(e.settings),s=null;return n?s=mt(t):o&&(s="*"),{adminAuthEnabled:n,aiFeaturesEnabled:o,allowWithoutAdminAuth:r.allowWithoutAdminAuth,permission:s,capabilities:cu(s,o)}}function mn(e){return(t,n,r)=>{if(!Ce(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 Qn(e){return(t,n,r)=>{if(!oe(e.settings)){if(Ce(e.settings)){r();return}n.status(403).json({error:"AI Access features are disabled",code:"ai_features_disabled"});return}if(mt(t)!=="*"){n.status(403).json({error:"Full admin permission (*) is required for this action",code:"insufficient_permissions"});return}r()}}function uu(e){return e.startsWith("aia_")}function pu(e){return uu(e)?!0:e.length<24}function fu(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 ra(e,t,n,r){if(!oe(e.settings)){r();return}let o={error:xe,code:qe},s=na(n,o,ce);e.auth.needsPermission("read")(t,n,i=>{if(s(),i){r(i);return}if(!n.headersSent){if(!t.user||!mt(t)){n.setHeader("WWW-Authenticate",ce),n.status(401).json(o);return}r()}})}function oa(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?(nr(t,r,l.link.kind),"wrong_kind"):(e.accessLink=l.link,"ok");if(l.status==="expired")return tr(e,t,o,l.expiresAt),"invalid";if(pu(d)){t.setHeader("WWW-Authenticate",ce);let c=fu(d);return t.status(401).json({error:Ot,code:er,...c?{hint:c}:{}}),"invalid"}return"continue"}function sa(e,t,n="rest"){return(r,o,s)=>{if(!Ce(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=oa(r,o,t,n,e.settings.httpAdminRoot);if(i==="ok"){s();return}i==="invalid"||i==="wrong_kind"||ra(e,r,o,s)}}function ia(e,t,n="rest"){return(r,o,s)=>{let i=oa(r,o,t,n,e.settings.httpAdminRoot);if(i==="ok"){s();return}i==="invalid"||i==="wrong_kind"||ra(e,r,o,s)}}var Be=q(()=>{"use strict";$t();ke();a(Ae,"normalizeHttpAdminRoot");a(Se,"mcpApiBase");a(Zn,"dashboardPath");a(ht,"mcpRpcPath");a(lu,"scopesFromRequest");a(mt,"resolvePermission");a(cu,"capabilitiesFor");a(na,"interceptNeedsPermissionUnauthorized");a(Ie,"requireAdminSession");a(Gr,"describeAccess");a(mn,"requireAiFeatures");a(Qn,"requireStarPermission");a(uu,"looksLikeAccessLinkToken");a(pu,"looksLikeFailedAccessLinkAttempt");a(fu,"truncatedTokenHint");a(ra,"continueAsAdminSessionOrAccessLinkMissing");a(oa,"tryAccessLinkBearer");a(sa,"requireAdminReadOrAccessLink");a(ia,"requireAdminSessionOrAccessLink")});function hu(e,t){let n=Ae(e);return`${n==="/"?"/mcp/a":`${n}/mcp/a`}/${t}`}function mu(e){return!!e.app.get("trust proxy")}function aa(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 gu(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 Mt(e){try{let n=new URL(`http://${e.includes(":")&&!e.startsWith("[")?`[${e}]`:e}`).hostname.replace(/^\[|\]$/g,"");if(!(0,ws.isIP)(n))return;if(n.startsWith("::ffff:")){let r=n.slice(7);if((0,ws.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 hn(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 Kr(e){bs=gu(e)}function la(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],!bu.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)||!wu.test(t)&&!yu.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 ua(e){let t=e?.trim().toLowerCase();return t==="http"||t==="https"?t:void 0}function vu(e){let t=e.socket;return t?.encrypted?"https":t?"http":ua(e.protocol)??"http"}function vs(e){if(bs)return bs;let t=mu(e),n=t?aa(e,"x-forwarded-host"):void 0,r=e.get("host")?.trim(),o=la(n)?n:la(r)?r:void 0;if(!o)return;let s=(t?ua(aa(e,"x-forwarded-proto")):void 0)??vu(e);try{return new URL(`${s}://${o}`).origin}catch{return}}function rr(e,t){let n=vs(e);return n?`${n}${t}`:t}function pa(e,t,n){return rr(e,hu(t,n))}function ks(e){return e.replace(/\/mcp\/a\/[^/?#]+/gi,"/mcp/a/<redacted>")}var ws,gn,ca,bs,da,yu,wu,bu,ve=q(()=>{"use strict";ws=require("net");Be();gn=8192,ca=1800*1e3;a(hu,"mcpAccessLinkPath");a(mu,"isTrustProxyEnabled");a(aa,"firstForwardedValue");a(gu,"normalizePublicBaseUrl");a(Mt,"canonicalizeIp");a(hn,"validateSettingsPublicBaseUrl");a(Kr,"bindPublicBaseUrl");da="[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?",yu=new RegExp(`^${da}(?:\\.${da})*$`),wu=/^(?:(?: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)$/,bu=/^\[[0-9A-Fa-f:.]+\]$/;a(la,"isValidHostHeader");a(ua,"validScheme");a(vu,"connectionScheme");a(vs,"requestPublicOrigin");a(rr,"absolutizePath");a(pa,"buildAccessLinkUrl");a(ks,"redactAccessLinkTokenInPath")});function Ru(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 As(e,t,n,r){e.setHeader("WWW-Authenticate",ce),e.status(401).json({error:t,code:n,...r?{hint:r}:{}})}function Pu(e){let t=e??"";for(;t.endsWith("/");)t=t.slice(0,-1);return`${t}/ai-access#links`}function _u(e,t,n,r){let o=rr(e,Pu(t));return{error:ku,code:Su,...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 tr(e,t,n,r,o){t.setHeader("WWW-Authenticate",ce),t.status(401).json(_u(e,n,r,o))}function Eu(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 Jr(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 Iu(e,t){let n=Cu;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: ${Jr("get_link")}`:e==="rest"?`This REST endpoint requires a rest token. This token is kind=${t} \u2014 ${n}. What to tell the user: ${Jr("rest")}`:`This MCP endpoint requires an mcp token. This token is kind=${t} \u2014 ${n}. What to tell the user: ${Jr("mcp")}`}function nr(e,t,n){e.setHeader("WWW-Authenticate",ce),e.status(403).json({error:`Access link kind mismatch: expected ${t}, got ${n}`,code:Tu,expectedKind:t,actualKind:n,hint:Iu(t,n),tellUser:Jr(t)})}function Dt(e,t,n){return(r,o,s)=>{if(!Ce(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=Ru(r.headers.authorization);if(i.kind==="basic"){As(o,Au,xu);return}if(i.kind==="missing"||i.kind==="other"){As(o,xe,qe);return}let d=t.validateResult(i.token);if(d.status==="expired"){tr(r,o,e.settings.httpAdminRoot,d.expiresAt);return}if(d.status!=="active"){As(o,Ot,er,Eu(i.token));return}if(d.link.kind!==n){nr(o,n,d.link.kind);return}r.accessLink=d.link,s()}}var Ot,ku,xe,Au,ce,qe,er,Su,xu,Tu,Cu,$t=q(()=>{"use strict";ve();ke();Ot="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.",ku="Access link token has expired. Ask the user for a new access link.",xe="Access link Bearer token is missing. Ask the user for a new access link and send Authorization: Bearer <token>.",Au="Authorization must use the Bearer scheme (Authorization: Bearer <access-link-token>), not Basic. Ask the user for a new access link.",ce='Bearer realm="node-red-ai-access", charset="UTF-8"',qe="access_link_missing",er="access_link_invalid",Su="token_expired",xu="bearer_scheme_required",Tu="wrong_token_kind";a(Ru,"parseAuthorizationHeader");a(As,"sendUnauthorized");a(Pu,"accessLinksPagePath");a(_u,"tokenExpiredBody");a(tr,"sendTokenExpired");a(Eu,"truncatedBearerHint");Cu="ask the owner: Node-RED /ai-access \u2192 Access tokens \u2192 create kind=rest/mcp (or get_link)";a(Jr,"whatToTellUserWrongTokenKind");a(Iu,"wrongTokenKindHint");a(nr,"sendWrongTokenKind");a(Dt,"requireAccessLink")});var te,X,Le,ze,Yr,Te=q(()=>{"use strict";te=class extends Error{static{a(this,"FlowRevisionConflictError")}code="version_mismatch";statusCode=409;expectedRev;currentRev;context;constructor(t,n,r="updateFlow"){super(`Flow revision conflict in ${r}: expectedRev '${t}' does not match current rev '${n}'. The flows changed since you last read them \u2014 re-read flows, re-apply your change, and retry.`),this.name="FlowRevisionConflictError",this.expectedRev=t,this.currentRev=n,this.context=r}},X=class extends Error{static{a(this,"FlowNotFoundError")}code="flow_not_found";statusCode=404;flowId;constructor(t){super(`Flow '${t}' was not found`),this.name="FlowNotFoundError",this.flowId=t}},Le=class extends Error{static{a(this,"AdapterUnavailableError")}code="adapter_unavailable";statusCode=502;constructor(t){super(t),this.name="AdapterUnavailableError"}},ze=class extends Error{static{a(this,"PluginMisconfiguredError")}code="plugin_misconfigured";statusCode=502;constructor(t){super(t),this.name="PluginMisconfiguredError"}},Yr="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 We(e,t){if(!e||!Lu.test(e)||e.includes(".."))throw new Error(`Invalid ${t}`);return e}function Xr(e){if(!e||!Nu.test(e)||e.includes(".."))throw new Error("Invalid proposal id");return e}function Ss(e,t){let n=(0,Ht.basename)(t);if(!n||n!==t||n.includes(".."))throw new Error("Invalid file name");let r=(0,Ht.resolve)(e),o=(0,Ht.resolve)(r,n);if(o!==r&&!o.startsWith(r+Ht.sep))throw new Error("Path escapes storage directory");return o}function fa(e,t=1e5){if(typeof e=="string")return e.replace(/\0/g,"").slice(0,t)}function it(e){let t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},n={};for(let o of Fu){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=fa(s,1e4);i!==void 0&&(n[o]=i)}for(let o of["nodes","configs"]){let s=t[o];if(!Array.isArray(s))continue;let i=JSON.stringify(s),d=Buffer.byteLength(i,"utf8");if(d>K)throw new le(`flow.${o}`,d,K);n[o]=JSON.parse(i)}let r=Buffer.byteLength(JSON.stringify(n),"utf8");if(r>K)throw new le("flow",r,K);return n}function ha(e){let t={};for(let n of Ou){if(!(n in e))continue;let r=e[n];if($u.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=fa(r,2e3);o!==void 0&&(t[n]=o)}return t}var Ht,Lu,Nu,Fu,Ou,$u,Zr=q(()=>{"use strict";Ht=require("path");se();Lu=/^[A-Za-z0-9_.-]+$/,Nu=/^prop-[A-Za-z0-9_-]+$/,Fu=["id","type","label","disabled","info","env"],Ou=["proposalId","flowId","expectedRev","resultRev","reason","code","currentRev","title","displayName","flowLabel","linkId","label","expiresAt","createdAt","ttlSeconds","kind","redactions","restoredRedactions","operation"],$u=new Set(["ttlSeconds","redactions","restoredRedactions"]);a(We,"assertSafeSegment");a(Xr,"assertSafeProposalId");a(Ss,"resolveUnderDir");a(fa,"sanitizeString");a(it,"sanitizeFlowTab");a(ha,"sanitizeAuditDetails")});function U(e){return e==="pending"||e==="conflict"}function to(e,t){return`oauth-${(0,eo.createHash)("sha256").update(`${e}\0${t}`).digest("hex")}`}function Ut(e){return e.kind==="oauth"?to(e.clientId,e.user):e.linkId}function Mu(e){return e==="pending"||e==="conflict"||e==="approved"||e==="rejected"||e==="failed"||e==="withdrawn"||e==="expired"||e==="superseded"}function Ge(e,t){return typeof e=="string"&&e.length>0?e.replace(/\0/g,"").slice(0,t):void 0}function Hu(e){if(!e||typeof e!="object"||Array.isArray(e))return;let t=e,n=Ge(t.rev,200),r=t.state;if(!n||r!=="clean"&&r!=="merged"&&r!=="conflict")return;let o=[];if(Array.isArray(t.conflicts))for(let d of t.conflicts.slice(0,200)){if(!d||typeof d!="object")continue;let l=d,c=Ge(l.kind,64),u=Ge(l.message,500);if(!c||!u)continue;let p={kind:c,message:u,choices:Array.isArray(l.choices)?l.choices.filter(g=>g==="live"||g==="proposed"):[]},f=Ge(l.nodeId,200);f&&(p.nodeId=f);let h=Ge(l.nodeType,200);h&&(p.nodeType=h);let m=Ge(l.nodeName,120);m&&(p.nodeName=m),Array.isArray(l.fields)&&(p.fields=l.fields.filter(g=>typeof g=="string"&&g.length>0).slice(0,100).map(g=>g.replace(/\0/g,"").slice(0,200))),o.push(p)}let s={rev:n,state:r,conflicts:o,liveChangedCount:typeof t.liveChangedCount=="number"&&Number.isFinite(t.liveChangedCount)?Math.max(0,Math.floor(t.liveChangedCount)):0,checkedAt:Ge(t.checkedAt,64)??""},i=Ge(t.mergedHash,64);return i&&/^[0-9a-f]+$/.test(i)&&(s.mergedHash=i),s}function ju(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 ma(e){if(!Array.isArray(e)||e.length===0)return;let t=[];for(let n of e.slice(0,Du)){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 eo,gt,ga,me,Y,jt,Re,Du,Ke,Qr,Je=q(()=>{"use strict";eo=require("crypto"),gt=require("fs"),ga=require("path");Zr();se();a(U,"isOpenProposalStatus");a(to,"oauthOwnerId");a(Ut,"proposalOwnerId");me=class extends Error{static{a(this,"ProposalNotFoundError")}code="proposal_not_found";statusCode=404;constructor(t){super(`Proposal '${t}' was not found`),this.name="ProposalNotFoundError"}},Y=class extends Error{static{a(this,"ProposalNotPendingError")}code="proposal_not_pending";statusCode=409;constructor(t,n){super(`Proposal '${t}' is '${n}', not pending`),this.name="ProposalNotPendingError"}},jt=class extends Error{static{a(this,"ProposalNotOwnedError")}code="proposal_not_owned";statusCode=403;constructor(t){super(`Proposal '${t}' was not created by this access link`),this.name="ProposalNotOwnedError"}},Re=class extends Error{static{a(this,"ProposalConcurrentApproveError")}code="proposal_approve_in_progress";statusCode=409;constructor(t){super(`Proposal '${t}' is already being approved`),this.name="ProposalConcurrentApproveError"}};a(Mu,"isProposalStatus");Du=50;a(Ge,"cleanText");a(Hu,"sanitizeRebaseInfo");Ke=class extends Error{static{a(this,"ProposalConflictError")}code="proposal_conflict";statusCode=409;conflicts;constructor(t,n){super(`Proposal '${t}' conflicts with changes made in the editor since it was submitted; resolve the conflicts before approving.`),this.name="ProposalConflictError",this.conflicts=n}};a(ju,"sanitizeRestoredSecrets");a(ma,"sanitizeProposalWarnings");Qr=class{constructor(t,n={}){this.paths=t;this.now=n.now??(()=>Date.now()),this.randomId=n.randomId??(()=>(0,eo.randomBytes)(12).toString("hex")),this.maxProposalBytes=n.maxProposalBytes??K}static{a(this,"ProposalStore")}inFlight=new Set;now;randomId;maxProposalBytes;fileFor(t){let n;try{n=Xr(t)}catch{throw new me(t)}return Ss(this.paths.proposals,`${n}.json`)}toDiskRecord(t){let n=Xr(t.id),r=We(String(t.expectedRev).slice(0,200),"expectedRev"),o=We(String(t.payload.flowId).slice(0,200),"flowId"),s=it(t.payload.flow),i=t.baseFlow!==void 0?it(t.baseFlow):void 0,d=t.createdBy.kind==="oauth"?{kind:"oauth",clientId:We(String(t.createdBy.clientId).slice(0,200),"clientId"),user:String(t.createdBy.user).replace(/\0/g,"").slice(0,200)}:{kind:"access-link",linkId:We(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:Mu(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=ma(t.warnings);c&&(l.warnings=c);let u=ju(t.restoredSecrets);if(u&&(l.restoredSecrets=u),t.deployPending&&U(l.status)){let f=Ge(t.deployPending.mergedHash,64),h=Ge(t.deployPending.reason,500);f&&/^[0-9a-f]+$/.test(f)&&h&&(l.deployPending={mergedHash:f,reason:h},t.deployPending.queueReleased===!0&&(l.deployPending.queueReleased=!0))}let p=Hu(t.rebase);if((p&&U(l.status)||p&&p.state!=="conflict")&&(l.rebase=p),t.decidedBy)if(t.decidedBy.kind==="access-link"){let f={kind:"access-link",linkId:We(String(t.decidedBy.linkId).slice(0,200),"linkId")};typeof t.decidedBy.label=="string"&&(f.label=t.decidedBy.label.replace(/\0/g,"").slice(0,200)),l.decidedBy=f}else{let f={kind:"admin"};typeof t.decidedBy.username=="string"&&(f.username=t.decidedBy.username.replace(/\0/g,"").slice(0,200)),l.decidedBy=f}return typeof t.reason=="string"&&(l.reason=t.reason.replace(/\0/g,"").slice(0,2e3)),typeof t.resultRev=="string"&&(l.resultRev=We(t.resultRev.slice(0,200),"resultRev")),l}writeAtomic(t,n){let r=this.fileFor(t),o=this.toDiskRecord(n),s=ft(o,this.maxProposalBytes,{kind:"proposal",pretty:!0});cn(this.paths.root,r,s)}create(t){let n=new Date(this.now()).toISOString(),r=Xr(`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=ma(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,gt.existsSync)(n))throw new me(t);return JSON.parse((0,gt.readFileSync)(n,"utf8"))}list(t){if(!(0,gt.existsSync)(this.paths.proposals))return[];let n=(0,gt.readdirSync)(this.paths.proposals).filter(o=>o.endsWith(".json")),r=[];for(let o of n){let s=(0,ga.basename)(o);if(!(s!==o||s.includes("..")))try{let i=JSON.parse((0,gt.readFileSync)(Ss(this.paths.proposals,s),"utf8"));if(t?.linkId&&Ut(i.createdBy)!==t.linkId||t?.status&&i.status!==t.status||t?.flowId&&i.payload.flowId!==t.flowId)continue;r.push(i)}catch{}}return r.sort((o,s)=>o.createdAt<s.createdAt?1:-1)}beginApprove(t){if(this.inFlight.has(t))throw new Re(t);let n=this.get(t);if(!U(n.status))throw new Y(t,n.status);return this.inFlight.add(t),n}releaseLock(t){this.inFlight.delete(t)}recordDeployPending(t,n){let r=this.get(t);if(!U(r.status))throw new Y(t,r.status);return n?r.deployPending=n:delete r.deployPending,r.status="pending",r.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,r),this.get(t)}markApproved(t,n,r){let o=this.get(t);return o.status="approved",delete o.deployPending,o.decidedBy=n,o.resultRev=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markRejected(t,n,r){if(this.inFlight.has(t))throw new Re(t);let o=this.get(t);return o.status="rejected",n&&(o.decidedBy=n),o.reason=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markFailed(t,n,r){let o=this.get(t);return o.status="failed",n&&(o.decidedBy=n),o.reason=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markWithdrawn(t,n,r){if(this.inFlight.has(t))throw new Re(t);let o=this.get(t);if(!U(o.status))throw new Y(t,o.status);return o.status="withdrawn",o.decidedBy=n,o.reason=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markClosed(t,n,r,o){let s=this.get(t);if(!U(s.status))throw new Y(t,s.status);return s.status=n,o&&(s.decidedBy=o),s.reason=r,s.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,s),this.get(t)}recordRebase(t,n){let r=this.get(t);if(!U(r.status))return r;let o=n.state==="conflict"?"conflict":"pending",s=r.rebase;return r.status===o&&s!==void 0&&s.rev===n.rev&&s.state===n.state&&s.mergedHash===n.mergedHash&&s.liveChangedCount===n.liveChangedCount&&JSON.stringify(s.conflicts)===JSON.stringify(n.conflicts)?r:(r.status=o,r.rebase=n,r.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,r),this.get(t))}updateBody(t,n){let r=this.get(t);if(!U(r.status))throw new Y(t,r.status);if(this.inFlight.has(t))throw new Re(t);return r.baseFlow=n.baseFlow,r.payload={flowId:r.payload.flowId,flow:n.flow},n.expectedRev!==void 0&&(r.expectedRev=n.expectedRev),n.title!==void 0&&(r.title=n.title),n.restoredSecrets!==void 0&&(n.restoredSecrets.length>0?r.restoredSecrets=n.restoredSecrets:delete r.restoredSecrets),n.warnings!==void 0&&(n.warnings.length>0?r.warnings=n.warnings:delete r.warnings),r.status="pending",delete r.rebase,r.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,r),this.get(t)}isApproving(t){return this.inFlight.has(t)}}});function ya(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 no(e,t,n){let r=e.find(d=>d.id===t);if(!r||r.type!=="tab"&&r.type!=="subflow")throw new X(t);let o={...r,id:t,type:r.type};delete o.nodes,delete o.configs,delete o.subflows,n.label!==void 0&&(o.label=n.label),n.disabled!==void 0&&(o.disabled=n.disabled),n.info!==void 0&&(o.info=n.info),n.env!==void 0&&(o.env=n.env);let s=[];for(let[d,l]of(n.nodes??[]).entries())s.push({...ya(l,`flowData.nodes[${d}]`),z:t});for(let[d,l]of(n.configs??[]).entries())s.push({...ya(l,`flowData.configs[${d}]`),z:t});return[...e.filter(d=>d.id!==t&&d.z!==t),o,...s]}function or(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 yn(e,t){if(Array.isArray(t)){let d=t.find(u=>u&&typeof u=="object"&&u.id===e&&(u.type==="tab"||u.type==="subflow"));if(!d)throw new X(e);let l=t.filter(u=>u&&typeof u=="object"&&u.id!==e&&u.z===e),c={id:e,type:d.type,nodes:l};return typeof d.label=="string"&&(c.label=d.label),typeof d.disabled=="boolean"&&(c.disabled=d.disabled),typeof d.info=="string"&&(c.info=d.info),c}if(!t||typeof t!="object")throw new X(e);let n=t,r=typeof n.id=="string"&&n.id.length>0?n.id:e,o=typeof n.type=="string"&&n.type.length>0?n.type:"tab",s=Array.isArray(n.nodes)?n.nodes:[],i={...n,id:r,type:o,nodes:s};return Array.isArray(n.configs)&&(i.configs=n.configs),i}var ro=q(()=>{"use strict";Te();a(ya,"asFlowNode");a(no,"replaceFlowTabInFlatFlows");a(or,"assertFlowsSnapshot");a(yn,"normalizeFlowTab")});function wa(e,t=new xs){return oo in e?e:{kind:e.kind,[oo]:t,async getFlows(){let r=await e.getFlows();return t.record(r),r},async getFlow(r){let o=await e.getFlow(r);if(!t.has(o.rev))try{t.record(await e.getFlows())}catch{}return o},async updateFlow(r){return e.updateFlow(r)}}}function Ts(e){return oo in e?e[oo]:void 0}function so(e,t){return{kind:t,async getFlows(){return{rev:e.rev,flows:JSON.parse(JSON.stringify(e.flows))}},async getFlow(n){let r=JSON.parse(JSON.stringify(e.flows)),o=r.find(l=>l.id===n&&(l.type==="tab"||l.type==="subflow"));if(!o)throw new X(n);let s=[],i=[];for(let l of r){if(l.id===n||l.z!==n)continue;(l.type!=="group"&&l.x===void 0&&l.y===void 0&&l.wires===void 0?i:s).push(l)}let d={id:n,type:o.type,nodes:s};for(let l of["label","disabled","info","env"])o[l]!==void 0&&(d[l]=o[l]);return i.length>0&&(d.configs=i),{rev:e.rev,flow:d}},async updateFlow(){throw new Error("snapshot adapter is read-only")}}}var Uu,xs,oo,Rs=q(()=>{"use strict";Te();Uu=24,xs=class{constructor(t=Uu){this.maxRevs=t}static{a(this,"RevHistory")}byRev=new Map;record(t){if(!(!t||typeof t.rev!="string"||!Array.isArray(t.flows))&&!this.byRev.has(t.rev))for(this.byRev.set(t.rev,{rev:t.rev,flows:JSON.parse(JSON.stringify(t.flows))});this.byRev.size>this.maxRevs;){let n=this.byRev.keys().next().value;if(n===void 0)break;this.byRev.delete(n)}}get(t){let n=this.byRev.get(t);return n?{rev:n.rev,flows:JSON.parse(JSON.stringify(n.flows))}:void 0}has(t){return this.byRev.has(t)}},oo=Symbol.for("ai-access.revHistory");a(wa,"withRevHistory");a(Ts,"revHistoryOf");a(so,"snapshotAdapter")});function Cs(e){return e?Bu.has(e)?!0:e.startsWith("subflow:"):!1}function yt(e,t="node.type"){if(e==null||e==="")throw new b(`${t} is required`,"missing_node_type");if(typeof e!="string")throw new b(`${t} must be a non-empty string (got ${typeof e})`,"invalid_node_type",{details:{type:e,got:typeof e}});if(!e.trim())throw new b(`${t} is required`,"missing_node_type");return e}function Vu(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 Yu(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 Zu(e){return Math.max(2,Math.floor(e.length/3))}function Is(e,t,n=5){if(typeof e!="string"||!e)return[];let r=Zu(e);return t.filter(s=>typeof s=="string"&&s!==e&&!Xu.has(s)&&!s.startsWith("_")&&!s.startsWith("subflow:")).map(s=>({c:s,dist:Vu(e,s),prefix:Yu(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 Qu(e){return e.endsWith("/")?e.slice(0,-1):e}function Ta(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,xa.createRequire)(e)}function ep(){try{let t=Ta()("@node-red/runtime/lib/settings");return typeof t.available=="function"&&t.available()===!0}catch{return!1}}function Ra(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 Ls(e=Ju,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 Pa(e={}){if(ep())try{let n=Ta()("@node-red/runtime");if(n.nodes&&typeof n.nodes.getNodeList=="function")return new Es(n.nodes)}catch{}if(e.baseUrl)return new _s({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 Ns(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 tp(e,t){let n=t.toLowerCase();return[e.type,e.module,e.category??"",e.description??""].join(`
|
|
4
|
+
`).toLowerCase().includes(n)}async function ao(e,t={}){let n=t.limit??qu,{byType:r}=await Ns(e),o=[...r.values()].sort((u,p)=>u.type.localeCompare(p.type)),s=t.q?.trim();s&&(o=o.filter(u=>tp(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 np(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 va(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 rp(e,t=2e3){let n=va(e,"script");return n=va(n,"style"),n=n.replace(/<[^>]+>/g," ").replace(/ /gi," ").replace(/[#*_`]+/g," ").replace(/\s+/g," ").trim(),n.length>t&&(n=`${n.slice(0,t-1)}\u2026`),n}function op(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:ka(e,d,i)}return ka(e,n.index,r)}catch{return null}}function ka(e,t,n){let r=e.slice(t),o=r.indexOf("{");if(o<0)return{type:n};let s=sr(r.slice(o));if(!s)return{type:n};let i=Aa(s,"category"),d=Aa(s,"color"),l=Sa(s,"inputs"),c=Sa(s,"outputs"),u=ip(s),p=[];if(u){for(let g of Object.values(u))if(g&&typeof g=="object"&&!Array.isArray(g)){let y=g.type;typeof y=="string"&&y.length>0&&p.push(y)}}let f,h=np(e,n);h&&(f=rp(h));let m={type:n};return i&&(m.category=i),d&&(m.color=d),l!==void 0&&(m.inputs=l),c!==void 0&&(m.outputs=c),u&&(m.defaults=u),p.length>0&&(m.requiredConfigNodeTypes=[...new Set(p)]),f&&(m.help=f),m}function sp(e){let t=e.trim();if(!t)return{};if(!/\n(?=#{1,3}\s*(inputs?|outputs?)\b)/i.test(`
|
|
5
|
+
${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(let l of r){let c=l.trim();if(!c)continue;let u=/^(#{1,3})\s*(inputs?|outputs?)\b[\s:]*\n?([\s\S]*)$/i.exec(c);if(u){let p=(u[2]??"").toLowerCase(),f=(u[3]??"").trim();p.startsWith("input")?s=f||c:i=f||c}else o||(o=c.replace(/^#{1,3}\s*/,"").trim())}let d={};return o&&(d.summary=o),s&&(d.inputs=s),i&&(d.outputs=i),!d.summary&&!d.inputs&&!d.outputs&&(d.text=t),d}function sr(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 Aa(e,t){let n=new RegExp(`\\b${t}\\s*:\\s*['"]([^'"]+)['"]`);return e.match(n)?.[1]}function Sa(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 ip(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=sr(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=sr(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 h=c.slice(f.index+f[0].length).trimStart();u.value=lp(h)}s[l]=u}return Object.keys(s).length>0?s:void 0}function ap(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 dp(e){return e==="_DEFAULT_"?"":e}function io(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=ap(t,o+1);s+=d.char,o=d.next;continue}if(i===r)break;s+=i,o+=1}return dp(s)}if(t.startsWith("[")){let r=_a(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=io(d);s.push(l);let c=Ps(d);i+=c>0?c:1}return s}if(t.startsWith("{")){let r=sr(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=io(o.slice(i));d=typeof c=="string"?c:"",i+=Ps(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]=io(l),i+=Ps(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 Ps(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=_a(t);return n+(o?.length??1)}if(t.startsWith("{")){let o=sr(t);return n+(o?.length??1)}let r=/^(?:-?\d+(?:\.\d+)?|true|false|null)/.exec(t);return r?n+r[0].length:n+1}function _a(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 lp(e){return io(e)}function cp(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 Fs(e,t){let n=e.toLowerCase();return t.includes(e)?e:t.find(r=>r.toLowerCase()===n)}async function Bt(e,t,n={}){if(!t||typeof t!="string")throw new b("Missing node type","missing_node_type");let r=n.includeHelp!==!1;if(Cs(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=cp(o,t);if(!i){let l=Fs(t,s);throw l&&l!==t?new b(`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 b(`Node type '${t}' was not found`,"node_type_not_found",{statusCode:404,details:{type:t,nearest:Is(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=op(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=sp(c.help)))}}catch{}return d}async function Ea(e,t){let{allNames:n}=await Ns(e),r=new Set(n),o=new Set,s=a(l=>{let c=yt(l,"node.type");if(o.has(c)||Cs(c)){o.add(c);return}if(o.add(c),r.has(c))return;let u=Fs(c,n);throw u&&u!==c?new b(`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 b(`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:Is(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 Ca(e,t){let n=yt(t,"node.type");if(Cs(n))return;let{allNames:r}=await Ns(e);if(r.includes(n))return;let o=Fs(n,r);throw o&&o!==n?new b(`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 b(`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:Is(n,r,5)}})}async function up(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 pp(e,t){let n=Date.now();if(Ne&&n-Ne.fetchedAt<Ku)return Ne.body;let r=Ne?.etag,o=await e(t,r);if(o.status===304&&Ne)return Ne={...Ne,fetchedAt:n,...o.etag?{etag:o.etag}:{}},Ne.body;if(!o.body||!Array.isArray(o.body.modules)){if(Ne)return Ne.body;throw new b("Node-RED catalogue unavailable","catalogue_unavailable",{statusCode:502})}return Ne={fetchedAt:n,body:o.body,...o.etag?{etag:o.etag}:{}},o.body}async function lo(e,t,n={}){let r=t.trim();if(!r)throw new b("q query parameter is required","missing_query");let o=n.limit??zu,s=n.catalogueUrl??Gu,i=n.catalogueFetcher??up,d=await pp(i,s),l=Array.isArray(d.modules)?d.modules:[],c=await e.listNodeSets(),u=new Set(c.flatMap(g=>{let y=[g.module,g.id];return g.id.includes("/")&&y.push(g.id.split("/")[0]??g.module),y.map(A=>A.toLowerCase())})),p=r.toLowerCase(),f=[];for(let g of l){let y=g.id.toLowerCase(),A=(g.keywords??[]).map(D=>String(D).toLowerCase()),x=(g.description??"").toLowerCase(),$=fp(p,y,A,x);if($<=0)continue;let L={module:g.id,installed:u.has(y)};typeof g.description=="string"&&(L.description=g.description),typeof g.version=="string"&&(L.version=g.version),typeof g.updated_at=="string"&&(L.updated=g.updated_at),typeof g.downloads=="number"&&(L.downloads=g.downloads),typeof g.url=="string"&&(L.link=g.url),f.push({item:L,score:$})}f.sort((g,y)=>y.score-g.score||(y.item.downloads??0)-(g.item.downloads??0)||g.item.module.localeCompare(y.item.module));let h=f.length,m=h>o;return{q:r,modules:f.slice(0,o).map(g=>g.item),total:h,truncated:m,hint:Wu}}function fp(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 xa,Bu,qu,zu,Wu,Gu,Ku,Ju,Xu,_s,ba,Es,Ne,qt=q(()=>{"use strict";xa=require("module");ie();Bu=new Set(["tab","subflow","group","junction"]),qu=100,zu=30,Wu="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.",Gu="https://catalogue.nodered.org/catalogue.json",Ku=3600*1e3,Ju=["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(Cs,"isStructuralNodeType");a(yt,"requireNodeTypeName");a(Vu,"editDistance");a(Yu,"sharedPrefixLength");Xu=new Set(["unknown","tab","subflow","group"]);a(Zu,"nearestDistanceCutoff");a(Is,"nearestNodeTypes");a(Qu,"trimTrailingSlash");a(Ta,"createPluginRequire");a(ep,"isRuntimeSettingsAvailable");a(Ra,"normalizeNodeSet");a(Ls,"createStaticNodeTypesAdapter");_s=class{static{a(this,"HttpNodeTypesAdapter")}baseUrl;accessToken;fetchImpl;constructor(t){this.baseUrl=Qu(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=Ra(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()}},ba={user:null,req:null},Es=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(ba))}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=Ra(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",...ba}));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(Pa,"createNodeTypesAdapter");a(Ns,"flattenInstalledTypes");a(tp,"matchesQuery");a(ao,"listNodeTypes");a(np,"extractHelpScriptBody");a(va,"removeHtmlBlocks");a(rp,"stripHelpText");a(op,"parseRegisterTypeFromHtml");a(ka,"parseRegisterTypeAt");a(sp,"splitHelpSections");a(sr,"extractBalancedObject");a(Aa,"matchStringProp");a(Sa,"matchNumberProp");a(ip,"parseDefaultsBlock");a(ap,"unescapeJsStringEscape");a(dp,"normalizeDefaultValue");a(io,"parseJsLiteral");a(Ps,"measureJsLiteralLength");a(_a,"extractBalancedArray");a(lp,"parseLooseValue");a(cp,"findSetForType");a(Fs,"findCaseInsensitiveType");a(Bt,"getNodeTypeDetail");a(Ea,"assertKnownNodeTypes");a(Ca,"assertKnownNodeType");Ne=null;a(up,"defaultCatalogueFetcher");a(pp,"loadCatalogue");a(lo,"searchPalette");a(fp,"paletteMatchScore")});function at(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 b(`${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 b(t.error,t.code,{statusCode:o,details:r})}function Os(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 hp(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 La(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 ${hp(c)}; node ids must be unique across all flows`,nodeId:l})}return o}function Ia(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 gp(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]);for(let r of n)if(!mp.has(r)&&JSON.stringify(e[r])!==JSON.stringify(t[r]))return!0;return!1}function yp(e,t){let n=Ia(t);if(!e)return new Set(n.keys());let r=Ia(e),o=new Set;for(let[s,i]of n){let d=r.get(s);(!d||gp(d,i))&&o.add(s)}return o}function Na(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(...Os(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{yt(c.type,"node.type")}catch(u){u instanceof b&&n.push({code:u.code,error:u.message,...typeof c.id=="string"?{nodeId:c.id}:{}})}"wires"in c&&wn(c.wires,n,typeof c.id=="string"?c.id:void 0)}return n}function wn(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 wp(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 Bt(t,n,{includeHelp:!1})).outputs}catch{return}}function bp(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 vp(e){return Array.isArray(e)&&e.some(t=>typeof t=="string"&&t.length>0)}function kp(e,t){return t<=0?!1:!("wires"in e)||e.wires===void 0?!0:Array.isArray(e.wires)?!e.wires.some(vp):!1}async function $s(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 wp(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(!wn(d.wires,r,d.id))continue;let p=d.wires;if(l!==void 0){let f=bp(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 h of f){let m=String(h);s.has(m)||r.push({code:"invalid_wires",error:`Wire target '${m}' 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:m})}}u&&l!==void 0&&kp(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 Ms(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 Ap(e,t){return e.filter(n=>n.type===t).map(n=>n.id)}async function Ds(e,t,n){let r=[],o=[...t];for(let s of e){let i;try{i=yt(s.type,"node.type")}catch{continue}let d;try{d=await Bt(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 m=p.required===!0||c.has(f),g=s[u],y=Ap(o,f);if(typeof g!="string"||g.length===0){m&&r.push({code:"missing_config_node",error:`Node '${s.id}' (${i}) has no ${f} on '${u}' yet \u2014 configure before deploy if needed`,nodeId:s.id,type:i,property:u,configType:f,candidates:y});continue}o.find(x=>x.id===g)?.type!==f&&r.push({code:"missing_config_node",error:`Node '${s.id}' property '${u}' does not resolve to a ${f} config yet`,nodeId:s.id,type:i,property:u,configType:f,candidates:y})}}return r}async function Fa(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(...Ms(r,n));let d=t.baseFlow?yp(t.baseFlow,e):void 0,l=await $s(r,t.nodeTypes,{...d?{softWarningNodeIds:d}:{}});if(s.push(...l.errors),i.push(...l.warnings),t.nodeTypes){let c=await Ds(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 mp,Hs=q(()=>{"use strict";ie();qt();a(at,"throwValidationIssues");a(Os,"collectDuplicateNodeIdIssues");a(hp,"describeLiveNode");a(La,"collectCrossFlowDuplicateIssues");mp=new Set(["x","y","w","h","g","id","z"]);a(Ia,"collectNodesMap");a(gp,"nodeContentChanged");a(yp,"changedNodeIds");a(Na,"collectFullFlowShapeIssues");a(wn,"assertValidWiresShape");a(wp,"resolveNodeOutputCount");a(bp,"wireCountIssue");a(vp,"portHasTargets");a(kp,"nodeIsUnwired");a($s,"collectWireValidation");a(Ms,"collectZMismatchIssues");a(Ap,"configCandidates");a(Ds,"collectConfigWarnings");a(Fa,"validateFlowTab")});function wt(e,t="node"){return typeof e=="string"&&e.trim()?e.trim():t}function co(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 $a(e,t){e[Oa]=t}function Tp(e){if(!e)return;let t=e[Oa];return t instanceof Map?t:void 0}function Rp(e){return typeof e!="string"||!e.trim()?new Set:new Set(e.split(",").map(t=>t.trim()).filter(Boolean))}function Pp(e){if(e===!0||e===1)return!0;if(typeof e=="string"){let t=e.trim().toLowerCase();return t==="1"||t==="true"||t==="yes"}return!1}function ge(e){let t=Rp(e.include);return Pp(e.include_payload)&&(t.add("payload"),t.add("baseFlow")),t}function V(e,t){let n={req:e};t!==void 0&&(n.httpAdminRoot=t);let r=Tp(e);return r&&(n.liveFlowLabels=r),n}function bn(e){return typeof e.name=="string"&&e.name.trim()?e.name.trim():typeof e.label=="string"&&e.label.trim()?e.label.trim():""}function ir(e){return`"${e.replace(/"/g,"'")}"`}function vn(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 _p(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 Ep(e,t){let n=bn(e),r=bn(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 js(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&&_p(d,i)&&o.push({before:d,after:i})}return{added:n,removed:r,modified:o}}function uo(e){return e===1?"node":"nodes"}function Cp(e){return e.length===1&&e[0]?`adds 1 ${wt(e[0].type)}`:`adds ${e.length} ${uo(e.length)}`}function Ip(e){return`deletes ${e} ${uo(e)}`}function Lp(e){return`edits ${e} ${uo(e)}`}function Np(e){let t=[];e.removed>0&&t.push(Ip(e.removed));let n=e.added??[],r=e.addedCount??n.length;r>0&&t.push(n.length===r?Cp(n):`adds ${r} ${uo(r)}`),(e.modified??0)>0&&t.push(Lp(e.modified??0));let o=t.length?t.join(", "):"replaces nodes",s=e.flowLabel?.trim();return s?`Replace ${s}: ${o}`:`Replace flow: ${o}`}function Fp(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=wt(u.type),f=bn(u);return f?`Add ${p} node ${ir(f)}`:`Add ${p} node`}if(n[0]){let u=n[0],p=wt(u.type),f=bn(u);return f?`Remove ${p} node ${ir(f)}`:`Remove ${p} node`}let s=r[0];if(!s)return;let i=wt(s.after.type,wt(s.before.type)),d=bn(s.before),l=bn(s.after);if(Ep(s.before,s.after)&&d&&l)return`Rename ${i} node ${ir(d)} \u2192 ${ir(l)}`;let c=l||d;return c?`Edit ${i} ${ir(c)}`:`Edit ${i}`}function Us(e,t){return typeof e?.label=="string"&&e.label.trim()?e.label.trim():typeof t?.label=="string"&&t.label.trim()?t.label.trim():""}function po(e,t){if(typeof e.flowLabel=="string"&&e.flowLabel.trim())return e.flowLabel.trim();let n=Us(e.payload?.flow,e.baseFlow);if(n)return n;let r=e.payload?.flowId;return r&&t?.has(r)?t.get(r)??"":""}function Ma(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 Bs(e,t){let n=js(vn(e??{}),vn(t)),r=n.added.length+n.removed.length+n.modified.length,o=Us(t,e);if(r===0)return o?`Update ${o}`:"Update flow";if(n.removed.length>0)return Np({...o?{flowLabel:o}:{},removed:n.removed.length,added:n.added,modified:n.modified.length});if(r>xp)return o?`${r} changes in ${o}`:`${r} changes`;let s=Fp(n)??(o?`Update ${o}`:"Update flow");return Ma(s,o)}function Op(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 qs(e,t){return`${e} \xB7 ${Mp(t)}`}function $p(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 Mp(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=$p(-n.getTimezoneOffset());return`${r} ${s} ${i}:${d} ${l}`}function ar(e){if(typeof e!="string")return;let t=e.replace(/\0/g,"").replace(/[\u0001-\u001F\u007F]/g," ").replace(/\s+/g," ").trim().slice(0,Sp).trim();return t.length>0?t:void 0}function Dp(e,t){let n=Ae(t);return`${n==="/"?"/ai-access":`${n}/ai-access`}#proposal=${encodeURIComponent(e)}`}function zs(e,t={}){let n=po(e,t.liveFlowLabels),r=ar(e.title),o=Bs(e.baseFlow,n&&!Us(e.payload.flow,e.baseFlow)?{...e.payload.flow,label:n}:e.payload.flow),s=js(vn(e.baseFlow??{}),vn(e.payload.flow)),i=Op(r,s.removed.length)?o:r??o;return Ma(i,n)}function Hp(e){return typeof e.storedTitle=="string"&&e.storedTitle?e.storedTitle:ar(e.title)}function pe(e,t={}){let n=zs(e,t),r=po(e,t.liveFlowLabels),o=Hp(e)??n,s=Dp(e.id,t.httpAdminRoot),i=t.req?rr(t.req,s):s;return{...e,title:n,storedTitle:o,flowLabel:r,displayName:qs(n,e.createdAt),reviewUrl:i,reviewPath:s}}function jp(e){let t=js(vn(e.baseFlow??{}),vn(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 Up(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 fe(e,t={}){let n=pe(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:Up(n.createdBy),changeSummary:jp(e)};return r.has("payload")&&(o.payload=n.payload),r.has("baseFlow")&&n.baseFlow!==void 0&&(o.baseFlow=n.baseFlow),n.warnings&&n.warnings.length>0&&(o.warnings=n.warnings),typeof e.payload.flowId=="string"&&(o.flowId=e.payload.flowId),e.restoredSecrets&&e.restoredSecrets.length>0&&(o.restoredSecrets=e.restoredSecrets),n.deployPending&&(o.deployPending=n.deployPending),n.rebase&&(o.rebase=n.rebase),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 Da(e,t={}){return e.map(n=>fe(n,t))}var Sp,xp,Oa,Fe=q(()=>{"use strict";ve();Be();Sp=80,xp=1;a(wt,"nodeTypeText");Oa=Symbol.for("ai-access.liveFlowLabels");a(co,"liveFlowLabelsFromFlows");a($a,"setRequestFlowLabels");a(Tp,"getRequestFlowLabels");a(Rp,"parseIncludeQuery");a(Pp,"isTruthyFlag");a(ge,"resolveIncludeFromQuery");a(V,"presentOptionsFor");a(bn,"nodeLabel");a(ir,"quoteName");a(vn,"collectNodes");a(_p,"nodeContentChanged");a(Ep,"isRenameOnly");a(js,"classifyFlowChanges");a(uo,"pluralNodes");a(Cp,"describeAddedClause");a(Ip,"describeRemovedClause");a(Lp,"describeModifiedClause");a(Np,"buildReplaceFlowTitle");a(Fp,"describeOneChange");a(Us,"flowLabelHint");a(po,"resolveFlowLabel");a(Ma,"ensureTitleIncludesFlowName");a(Bs,"autoGenerateProposalTitle");a(Op,"shouldRefreshDisplayTitle");a(qs,"buildProposalDisplayName");a($p,"formatUtcOffset");a(Mp,"formatProposalSubmittedAt");a(ar,"sanitizeProposalTitle");a(Dp,"buildProposalReviewPath");a(zs,"resolveProposalTitle");a(Hp,"storedTitleOf");a(pe,"presentProposal");a(jp,"buildChangeSummary");a(Up,"presentCreatedByForApi");a(fe,"presentProposalForApi");a(Da,"presentProposalsForApi")});function Ha(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 qp(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]),r=[];for(let o of n)Bp.has(o)||JSON.stringify(e[o])!==JSON.stringify(t[o])&&r.push(o);return r.sort()}function ja(e){let t=Ha(e.baseFlow??{}),n=Ha(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=qp(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 zp(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 Ve(e,t,n){let r=ja(t),o=[];for(let s of e.list()){if(s.id===t.id||!U(s.status)||s.payload.flowId!==t.payload.flowId)continue;let i=zs(s),d=n?Ut(s.createdBy)===n:Ut(s.createdBy)===Ut(t.createdBy),l=zp(r,ja(s));if(l.nodeIds.length===0)continue;let c={id:s.id,title:i,displayName:qs(i,s.createdAt),mine:d};l.nodeIds.length>0&&(c.overlappingNodeIds=l.nodeIds),l.fields.length>0&&(c.overlappingFields=l.fields),o.push(c)}return{conflictingProposals:o}}function Ye(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 ue(e){return e>0?{autoClosedCount:e}:{}}var Bp,zt=q(()=>{"use strict";Je();Fe();Bp=new Set(["x","y","w","h","g","id","z"]);a(Ha,"collectNodes");a(qp,"changedFieldKeys");a(ja,"computeProposalTouches");a(zp,"overlapBetween");a(Ve,"findConflictingProposals");a(Ye,"conflictWarningFields");a(ue,"autoClosedFields")});function kn(e){let t=e.join("\0"),n=fo.get(t);if(n)return n;let r=[...new Set(e.map(d=>jr(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=jr(d),c=l[l.length-1];if(!c||Wp.has(c))return!1;for(let u of r){let p=`${u}S`;for(let f=0;f<l.length;f++){let h="";for(let g=f;g<l.length;g++){if(h+=l[g],h===u||h===p)return!0;if(h.length>p.length)break}let m=l[f]??"";if(u.length>=6&&(m.endsWith(u)||m.endsWith(p)))return!0}}return!1}}return a(i,"matchUncached"),fo.size>32&&fo.clear(),fo.set(t,s),s}function dt(){return ta()?.getEffective()?.secretNamePatterns??Ft}function cr(e){let t=e.trim();return!(!t||t===J||Gp.has(t.toLowerCase())||/^\$\{[A-Za-z0-9_]+\}$/.test(t))}function Jp(e){let t=qa.get(e);return t||(t=Vp(e),qa.set(e,t)),t}function Vp(e){return[{hints:["Buffer.from","btoa"],re:/\b(?:Buffer\.from|btoa)\([ \t]{0,8}(?<q>["'`])[^"'`:\s]{1,256}:(?<s>[^"'`\s]{1,1024})\k<q>/dg,accept:a(t=>!t.includes("${"),"accept")},{hints:["PRIVATE KEY-----"],re:/-----BEGIN ((?:[A-Z0-9]{1,16} ){0,4})PRIVATE KEY-----(?<s>[^-]{1,16384})-----END \1PRIVATE KEY-----/dg},{hints:["AKIA","ASIA","ABIA","ACCA"],re:/(?<![A-Za-z0-9])(?<s>(?:AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{16})(?![A-Za-z0-9])/dg},{hints:["ghp_","gho_","ghu_","ghs_","ghr_"],re:/(?<![A-Za-z0-9_])(?<s>gh[pousr]_[A-Za-z0-9]{36,255})(?![A-Za-z0-9_])/dg},{hints:["github_pat_"],re:/(?<![A-Za-z0-9_])(?<s>github_pat_[A-Za-z0-9_]{22,255})(?![A-Za-z0-9_])/dg},{hints:["xox"],re:/(?<![A-Za-z0-9_-])(?<s>xox[abposr]-[A-Za-z0-9-]{10,512})/dg},{hints:["hooks.slack.com"],re:/hooks\.slack\.com\/services\/(?<s>[A-Za-z0-9_/]{16,512})/dg},{hints:["sk-"],re:/(?<![A-Za-z0-9_-])(?<s>sk-[A-Za-z0-9_-]{20,512})/dg},{hints:["k_live_","k_test_"],re:/(?<![A-Za-z0-9_])(?<s>[sr]k_(?:live|test)_[A-Za-z0-9]{16,512})/dg},{hints:["AIza"],re:/(?<![A-Za-z0-9_-])(?<s>AIza[0-9A-Za-z_-]{35})(?![A-Za-z0-9_-])/dg},{hints:["eyJ"],re:/(?<![A-Za-z0-9_-])(?<s>eyJ[A-Za-z0-9_-]{5,8192}\.[A-Za-z0-9_-]{5,8192}\.[A-Za-z0-9_-]{5,8192})/dg},{hints:["earer"],re:/\b[Bb]earer[ \t]{1,8}(?<s>(?=[A-Za-z0-9\-._~+/]{0,4096}[0-9\-._~+/])[A-Za-z0-9\-._~+/]{16,4096}={0,3})/dg},{hints:["asic"],re:/\b[Bb]asic[ \t]{1,8}(?<s>(?=[A-Za-z0-9+/]{0,4096}[0-9+/])[A-Za-z0-9+/]{8,4096}={0,2})(?![A-Za-z0-9+/])/dg},{hints:["@"],re:/\b[A-Za-z][A-Za-z0-9+.-]{1,20}:\/\/[^\s:@/'"`<>]{1,256}:(?<s>[^\s@/'"`<>]{1,1024})@/dg,accept:a(t=>!/^(?:\$\{|\{\{|<|%)/.test(t)&&t!==J,"accept")},{hints:["?","&"],re:/[?&](?<k>[A-Za-z_][\w.-]{0,63})=(?<s>[^&#\s"'`<>]{1,2048})/dg,accept:a((t,n)=>e.matches(n.groups?.k??"")&&cr(ef(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:cr(t)},"accept")}]}function Xp(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 Zp(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||Xp(e)>=3}function Qp(e){let t=e.patterns.filter(r=>Yp.includes(r.toUpperCase())),n=kn(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??"")&&cr(r)&&!r.startsWith("$")&&!r.startsWith("{{")&&(n.matches(o.groups?.k??"")||Zp(r))&&!/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(r)&&o.input.charAt(o.indices?.groups?.s?.[1]??0)!=="(","accept")}}function ef(e){try{return decodeURIComponent(e)}catch{return e}}function tf(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 nf(e,t){if(!e.includes(";")||!e.includes("="))return[];let n=[],r=0;for(;r<=e.length;){let o=e.indexOf(`
|
|
7
|
+
`,r);o<0&&(o=e.length);let s=[],i=r,d=!1;for(;i<o;){let l=e.indexOf(";",i);l<0||l>o?l=o:d=!0;let c=tf(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)||cr(e.slice(l.vs,l.ve))&&n.push({start:l.vs,end:l.ve});r=o+1}return n}function rf(e,t,n={}){if(e.length===0)return{text:e,secrets:[],spans:[]};let r=[],o=Jp(t);if(!n.code){let u=za.get(t);u||(u=Qp(t),za.set(t,u)),o=[...o,u]}r.push(...nf(e,t));for(let u of o){if(u.hints&&!u.hints.some(f=>e.includes(f)))continue;u.re.lastIndex=0;let p;for(;(p=u.re.exec(e))!==null;){let f=p.indices?.groups?.s,h=p.groups?.s;f&&h!==void 0&&h.length>0&&(!u.accept||u.accept(h,p))&&r.push({start:f[0],end:f[1]}),p[0].length===0&&u.re.lastIndex++}}if(r.length===0)return{text:e,secrets:[],spans:[]};r.sort((u,p)=>u.start-p.start||p.end-u.end);let s=[];for(let u of r){let p=s[s.length-1];p&&u.start<p.end?p.end=Math.max(p.end,u.end):s.push({...u})}let i="",d=0,l=[],c=[];for(let u of s){let p=e.slice(u.start,u.end);p!==J&&(i+=e.slice(d,u.start)+J,l.push(p),c.push(u),d=u.end)}return i+=e.slice(d),{text:i,secrets:l,spans:c}}function Vs(e,t){let n=[],r=0,o=a(s=>{let i=wo(s);for(let d=0;d<i;d++)n.push({kind:"literal",value:J})},"literals");for(let s of t)o(e.slice(r,s.start)),n.push({kind:"secret",value:e.slice(s.start,s.end)}),r=s.end;return o(e.slice(r)),n}function Z(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function of(e){return Z(e)&&typeof e.id=="string"&&typeof e.type=="string"}function yo(e,t){let n=new Gs(t,e.id),r=e[dr];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!==dr&&(Ka.has(s)?o[s]=i:Ya.has(s)&&Array.isArray(i)?o[s]=i.map(d=>Ys(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[dr]=n.paths),o}function Ys(e,t){return Array.isArray(e)?e.map(n=>Ys(n,t)):of(e)?yo(e,t):e}function bt(e,t=dt()){let n={matcher:kn(t),count:0};return{value:Ys(e,n),count:n.count}}function Ks(e){if(Array.isArray(e)){let t=e.map(n=>({item:Ks(n),key:vt(n)}));return t.every(n=>n.key!==void 0)&&new Set(t.map(n=>n.key)).size===t.length&&t.sort((n,r)=>(n.key??"").localeCompare(r.key??"")),t.map(n=>n.item)}return Z(e)?Object.fromEntries(Object.entries(e).map(([t,n])=>[t,Ks(n)])):e}function Xa(e,t){let n=a(s=>{let i=new Map;return yo(Ks(s),{matcher:kn(dt()),count:0,secrets:i}),i},"values"),r=n(e),o=n(t);return[...new Set([...r.keys(),...o.keys()])].some(s=>JSON.stringify(r.get(s))!==JSON.stringify(o.get(s)))}function Xs(e,t=dt()){return e.map(n=>{if(n.nodeName===void 0)return{...n};let r=bt({id:"conflict",type:"node",name:n.nodeName},t);return{...n,nodeName:r.value.name}})}function lt(e,t=dt()){let n={...e,payload:{...e.payload,flow:bt(e.payload.flow,t).value}};return e.baseFlow!==void 0&&(n.baseFlow=bt(e.baseFlow,t).value),e.rebase&&(n.rebase={...e.rebase,conflicts:Xs(e.rebase.conflicts,t)}),n}function kt(e,t=dt){if(Za(e))return e;let n=t===dt,r=n?Wa.get(e):void 0;if(r)return r;let o={kind:e.kind,[Zs]:e,async getFlows(){let s=await e.getFlows();return{rev:s.rev,flows:bt(s.flows,t()).value}},async getFlow(s){let i=await e.getFlow(s);return{rev:i.rev,flow:bt(i.flow,t()).value}},updateFlow:a(s=>e.updateFlow(s),"updateFlow")};return n&&Wa.set(e,o),o}function Za(e){return Zs in e}function Oe(e){return Za(e)?e[Zs]:e}function Xe(e,t){return e.accessLink||e.auth?kt(t):t}function wo(e){let t=0,n=e.indexOf(J);for(;n!==-1;)t++,n=e.indexOf(J,n+J.length);return t}function ur(e){let t=e.split(J),n=[];for(let r=1;r<t.length;r++){let o=t[r-1]??"",s=t[r]??"",i=o.lastIndexOf(`
|
|
8
8
|
`),d=s.indexOf(`
|
|
9
|
-
`);n.push({before:i>=0?o.slice(i+1):o,after:d>=0?s.slice(0,d):s})}return n}function Cu(e,t,n,r){if(e===t)return{text:n,restored:Fr(e)};let o=Fi(e),s=Fi(t);if(o.length>s.length)return{error:`contains ${o.length} [REDACTED] placeholders but the original had ${s.length}`};let i=new Set,d=e.split(te),l=d[0]??"";for(let c=0;c<o.length;c++){let u=o[c];if(!u)break;let p=s.findIndex((f,m)=>!i.has(m)&&f.before===u.before&&f.after===u.after);if(p<0)return{error:"a [REDACTED] placeholder is not on an unchanged original line"};i.add(p),l+=(r[p]??te)+(d[c+1]??"")}return{text:l,restored:o.length}}function Oi(e){if(re(e))for(let t of Lu){let n=e[t];if(typeof n=="string"&&n.length>0)return`${t}=${n}`}}function Lr(e,t,n,r,o,s){if(typeof e=="string"){if(!e.includes(te))return e;let i=s.secrets.get(`${r}\0${o}`);if(typeof t=="string"&&typeof n=="string"&&i?.length){let d=Cu(e,t,n,i);return"error"in d?(s.unresolved.push({nodeId:r,property:o,reason:d.error}),e):(s.restored+=d.restored,s.restoredFields.push({nodeId:r,property:o}),d.text)}return s.unresolved.push({nodeId:r,property:o,reason:"no redacted secret exists at this node id and field"}),e}if(Array.isArray(e)){let i=Array.isArray(t)?t:[],d=Array.isArray(n)?n:[],l=new Set;return e.map(c=>{let u=re(c)&&c.redacted===!0?Xo(c,"redacted"):c,p=Oi(u),f=-1;if(p!==void 0){let m=d.map((h,g)=>Oi(h)===p&&!l.has(g)?g:-1).filter(h=>h>=0);m.length>0&&(f=m[0]??-1)}return f>=0?(l.add(f),Lr(u,i[f],d[f],r,`${o}[${f}]`,s)):Lr(u,void 0,void 0,r,`${o}[?]`,s)})}if(re(e)){let i=re(t)?t:{},d=re(n)?n:{},l={};for(let[c,u]of Object.entries(e))l[c]=Lr(u,i[c],d[c],r,o?`${o}.${c}`:c,s);return l}return e}function Xo(e,t){let n={...e};return delete n[t],n}function Iu(e){let t=Xo(e,$n);return Array.isArray(t.env)&&(t.env=t.env.map(n=>!re(n)||n.redacted!==!0?n:Xo(n,"redacted"))),t}function zi(e,t,n,r){let o=typeof e.id=="string"?e.id:"",s=o?t.get(o):void 0,i={},d=s?Ui({...s,id:o},r):void 0;for(let[l,c]of Object.entries(e))if(l!==$n){if(Hi.has(l)&&Array.isArray(c)){i[l]=c.map(u=>re(u)?zi(u,t,n,r):u);continue}if(Mi.has(l)){i[l]=c;continue}i[l]=Lr(c,d?.[l],s?.[l],o,l,n)}return Iu(i)}function Wi(e,t,n=Kt()){let r=new Map;for(let l of t)re(l)&&typeof l.id=="string"&&r.set(l.id,l);let o=new Map,s={matcher:Nr(n),count:0,secrets:o},i={secrets:o,restored:0,restoredFields:[],unresolved:[]};return{flow:zi(e,r,i,s),restored:i.restored,restoredFields:i.restoredFields,unresolved:i.unresolved}}function $u(e){return typeof e.type=="string"&&Fu.has(e.type)?!0:Ou.some(t=>typeof e[t]=="string"&&e[t]!=="")}function $i(e){let t=new Set,n=a((r,o)=>{if(!(o>12)){if(typeof r=="string"){if(r.length>ji)return;let s=/(?:\b(?:env|global|flow)\.get|\$env)\([ \t]{0,8}["'`]([^"'`\r\n]{1,128})["'`]|\$\{([A-Za-z_][\w.-]{0,127})\}/g,i;for(;(i=s.exec(r))!==null;)t.add(i[1]??i[2]??"");return}if(Array.isArray(r)){for(let s of r)n(s,o+1);return}if(re(r))for(let[s,i]of Object.entries(r))[`${s}Type`,s==="v"?"vt":"",s==="to"?"tot":"",s==="value"?"type":"",s==="valueValue"?"valueType":"",s==="keyValue"?"keyType":""].some(c=>c&&typeof r[c]=="string"&&Mu.has(r[c]))&&typeof i=="string"&&t.add(i),n(i,o+1)}},"walk");return n(e,0),t.delete(""),t}function Mn(e,t){return JSON.stringify(e)===JSON.stringify(t)}function Gi(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]);for(let r of n)if(!(Du.has(r)||r==="nodes"||r==="configs")&&!Mn(e[r],t[r]))return!0;return!1}function ju(e,t,n){let r=new Set([n]),o=typeof e.g=="string"?e.g:void 0;for(let s=0;o&&s<32&&!r.has(o);s++){r.add(o);let i=t.get(o);o=i&&typeof i.g=="string"?i.g:void 0}for(let s of Object.values(e))typeof s=="string"&&s!==e.id&&t.get(s)?.z===void 0&&t.has(s)&&r.add(s);for(let[s,i]of t)i.type==="global-config"&&r.add(s);return[...r]}function Hu(e,t){return e.filter(n=>n.nodeId===t).map(n=>n.property).join(", ")}function Uu(e,t,n,r){let o=[],s=Nu.filter(d=>!Mn(n[d],r[d]));s.length>0&&o.push({code:"secret_destination_changed",error:`SECURITY: node ${e} holds a secret (${t}) restored from the live flow, and this proposal changes where it sends data (${s.join(", ")}). Verify the new destination before approving.`,nodeId:e,property:s.join(",")});let i=[...Di].filter(d=>!Mn(n[d],r[d]));return i.length>0&&o.push({code:"secret_node_code_changed",error:`Node ${e} holds a secret (${t}) and its code changed (${i.join(", ")}). Check the new code does not send the secret anywhere unexpected.`,nodeId:e,property:i.join(",")}),Mn(n.wires,r.wires)||o.push({code:"secret_wires_changed",error:`SECURITY: node ${e} holds a secret (${t}) and its output wires changed. Check where its messages (which may carry the secret) now go.`,nodeId:e,property:"wires"}),o}function Bu(e,t,n,r,o,s){let i=[];for(let d of ju(t,n,o)){if(d===e||s.has(d))continue;let l=n.get(d),c=d===o,u=r.get(d)??(l&&!c&&l.z===void 0?l:void 0);if(!(c?!Mn(l?.env,u?.env):l!==void 0&&u!==void 0&&Gi(l,u)))continue;s.add(d);let f=c?"tab":typeof l?.type=="string"?l.type:"node";i.push({code:"secret_scope_changed",error:`SECURITY: ${f} ${d} scopes or configures secret-holding node ${e} (env vars like \${HOST} or a config node) and it changed. Check URLs/hosts it resolves to.`,nodeId:d,property:c?"env":"config"})}return i}function qu(e,t,n,r,o){let s=[],i=[];for(let[d,l]of n){if(d===r)continue;let c=t.get(d);if(c&&!Gi(c,l))continue;e.includes(d)||i.push(d);let u=typeof l.type=="string"?l.type:"node";$u(l)&&!e.includes(d)&&s.push({code:"outbound_node_changed",error:`SECURITY: this proposal ${c?"changes":"adds"} outbound-capable node ${d} (${u}). Check where it sends data before approving.`,nodeId:d,property:"type"});let p=c?$i(c):new Set,f=[...$i(l)].filter(m=>!p.has(m)&&o.matches(m));f.length>0&&s.push({code:"secret_reference_added",error:`SECURITY: node ${d} (${u}) now reads secret-named value(s) ${f.slice(0,5).join(", ")} (env / global / flow). The AI cannot see these values but the flow will use them at runtime; check they are not sent anywhere unexpected.`,nodeId:d,property:f.slice(0,5).join(",")})}if(e.length===0)return s;for(let[d,l]of t)l.z===r&&!n.has(d)&&i.push(d);if(i.length>0){let d=i.slice(0,10).join(", ")+(i.length>10?", \u2026":"");s.push({code:"secret_tab_changed",error:`This proposal restores secrets in this tab and also changes ${i.length} other node(s) (${d}). Review every change: any of them could route secret data elsewhere.`,nodeId:r,property:"nodes"})}return s}function zu(e,t){let n=new Map;n.set(t,e);for(let r of["nodes","configs"]){let o=e[r];if(Array.isArray(o))for(let s of o)re(s)&&typeof s.id=="string"&&n.set(s.id,s)}return n}function Ki(e,t,n,r=Kt()){let o=[...new Set(e.map(u=>u.nodeId))],s=String(t.id),i=new Map;for(let u of n)re(u)&&typeof u.id=="string"&&i.set(u.id,u);let d=zu(t,s),l=[],c=new Set;for(let u of o){let p=i.get(u),f=d.get(u);!p||!f||(l.push(...Uu(u,Hu(e,u),p,f)),l.push(...Bu(u,p,i,d,s,c)))}return l.push(...qu(o,i,d,s,Nr(r))),l}var $n,gu,yu,Vo,Mi,wu,Di,ji,Hi,Ei,Ci,Cr,Li,ku,Ii,Yo,Qo,Ni,Lu,Nu,Fu,Ou,Mu,Du,nt=U(()=>{"use strict";Sn();ue();Sn();$n="_aiRedactions",gu=new Set(["TYPE","TYPES","MODE","METHOD","FIELD","FIELDS","PROP","PROPERTY","FORMAT","TIMEOUT","EXPIRY","EXPIRES","TTL","LENGTH","LEN","SIZE","COUNT","ENABLED","DISABLED","ENABLE","USE","URL","URI","ENDPOINT","HOST","PORT","PATH","HEADER","NAME","LABEL","FILE"]),yu=new Set(["basic","digest","bearer","none","true","false","null","undefined","oauth","oauth2","jwt","apikey","api_key","token","password","header","query","body","cookie","msg","flow","global","env","str","num","json","bool","other"]),Vo=new Set(["env","msg","flow","global","jsonata","bool","date","node","cred"]),Mi=new Set(["id","type","z","g","x","y","w","h","wires","inputs","outputs","links","scope","icon","color","style","d","l","in","out","rev"]),wu=new Set(["name","label","info"]),Di=new Set(["func","initialize","finalize"]),ji=256*1024,Hi=new Set(["nodes","configs","subflows"]),Ei=["name","p","keyValue","key","property","k"],Ci={value:"type",v:"vt",to:"tot",valueValue:"valueType"},Cr=new Map;a(Nr,"createSecretNameMatcher");a(Kt,"currentSecretNamePatterns");a(Dn,"isSecretLikeValue");Li=new WeakMap;a(bu,"contentRules");a(vu,"buildContentRules");ku=["PASSWORD","PASSWD","PASSPHRASE","SECRET","CLIENT_SECRET","TOKEN","API_KEY","APIKEY","PRIVATE","CREDENTIAL","PWD","PW"];a(Au,"shannonEntropy");a(Su,"looksRandom");a(xu,"unquotedRule");a(Tu,"decodeURIComponentSafe");a(Ru,"parseConnSegment");a(_u,"connectionStringSpans");Ii=new WeakMap;a(Pu,"scanTextForSecrets");a(re,"isPlainObject");a(Eu,"isFlowEntity");Yo=class{constructor(t,n){this.ctx=t;this.entityId=n}static{a(this,"EntityRedactor")}paths=[];record(t,n){this.paths.includes(t)||this.paths.push(t),this.ctx.count+=n.length||1,this.ctx.secrets?.set(`${this.entityId}\0${t}`,n)}string(t,n,r){if(r&&Dn(t)||t.length>ji)return this.record(n,[t]),te;let o=n.split(/[.[]/,1)[0]??"",s=Pu(t,this.ctx.matcher,{code:Di.has(o)});return s.secrets.length>0?(this.record(n,s.secrets),s.text):t}env(t,n){return t.map((r,o)=>{if(!re(r))return r;let s=typeof r.type=="string"?r.type:"";if(s==="cred"||typeof r.value!="string"||Vo.has(s))return r;let i=typeof r.name=="string"?r.name:"",d=r.value,l=this.string(d,`${n}[${o}].value`,this.ctx.matcher.matches(i));return l===d?r:{...r,value:l,redacted:!0}})}value(t,n,r,o){if(typeof t=="string"){if(wu.has(r))return this.string(t,n,!1);let s=o?.[`${r}Type`],i=typeof s=="string"&&Vo.has(s);return this.string(t,n,!i&&this.ctx.matcher.matches(r))}return Array.isArray(t)?r==="env"?this.env(t,n):t.map((s,i)=>this.value(s,`${n}[${i}]`,"",void 0)):re(t)?this.object(t,n):t}object(t,n){let r=!1;for(let s of Ei){let i=t[s];if(typeof i=="string"&&this.ctx.matcher.matches(i)){r=!0;break}}let o={};for(let[s,i]of Object.entries(t)){let d=n?`${n}.${s}`:s;if(r&&s in Ci&&typeof i=="string"){let l=Ci[s],c=l?t[l]:void 0,u=typeof c=="string"&&Vo.has(c);o[s]=this.string(i,d,!u);continue}if(Ei.includes(s)&&typeof i=="string"){o[s]=this.string(i,d,!1);continue}o[s]=this.value(i,d,s,t)}return o}};a(Ui,"redactEntity");a(Zo,"redactValueInner");a(Ir,"redactFlowData");a(Jt,"redactProposalForAi");Qo=Symbol.for("ai-access.rawFlowAdapter"),Ni=new WeakMap;a(bt,"aiRedactingAdapter");a(Bi,"isRedactingAdapter");a(qi,"rawFlowAdapter");a(Me,"adapterForRequest");a(Fr,"countRedactionPlaceholders");a(Fi,"placeholderContexts");a(Cu,"restoreString");Lu=["name","keyValue","p","property","key","k"];a(Oi,"stableElementKey");a(Lr,"restoreWalk");a(Xo,"withoutKey");a(Iu,"stripMarkers");a(zi,"restoreEntity");a(Wi,"restoreRedactedPlaceholders");Nu=["type","url","host","hostname","server","broker","port","path","method","headers","endpoint","uri","protocol","proxy","tls","to","ret"],Fu=new Set(["http request","http response","link out","link call","mqtt out","mqtt-broker","tcp out","tcp request","udp out","e-mail","websocket out","websocket-client","websocket-listener","function","exec","file"]),Ou=["url","host","hostname","server","broker","endpoint","uri"];a($u,"isOutboundNode");Mu=new Set(["env","global","flow"]);a($i,"referencedNames");Du=new Set(["x","y","w","h","l"]);a(Mn,"sameJson");a(Gi,"nodeChanged");a(ju,"scopeIds");a(Hu,"restoredFieldList");a(Uu,"secretNodeWarnings");a(Bu,"scopeWarnings");a(qu,"tabWarnings");a(zu,"proposedNodeMap");a(Ki,"secretChangeWarnings")});var ta={};Os(ta,{applyFlowNodeOps:()=>vt,applyReplaceTextToValue:()=>ea,buildPatchedFlowTab:()=>jn,collectSetProps:()=>es,decodeBase64UrlJson:()=>Hn,findTextOccurrences:()=>Qi,interpretPatch:()=>Un});function Zi(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function Wu(e){if(typeof e!="object"||e===null||Array.isArray(e))throw new y(`add op needs node as an object (got ${Zi(e)})`,"invalid_node")}async function jn(e,t,n){return vt(e,t,n.map(r=>({op:"set",nodeId:r.nodeId,set:r.set})))}function Qi(e,t){if(!t)return[];let n=[],r=0;for(;r<=e.length;){let o=e.indexOf(t,r);if(o<0)break;n.push(o),r=o+t.length}return n}function ea(e,t,n,r,o){if(Xi.has(t))throw new y(`Cannot change protected node property '${t}' via replace_text`,"protected_property");if(typeof n!="string"||n.length===0)throw new y("find must be a non-empty string","missing_find");if(typeof r!="string")throw new y("replace must be a string","invalid_replace");if(e===void 0)throw new y(`Field '${t}' was not found on the node`,"field_not_found");if(typeof e!="string")throw new y(`Field '${t}' is not a string (got ${e===null?"null":typeof e})`,"field_not_string");let s=Qi(e,n);if(s.length===0)throw new y(`Text not found in field '${t}'`,"text_not_found",{details:{matchCount:0}});let i=o;if(i===void 0){if(s.length>1)throw new y(`Found ${s.length} matches in '${t}'; pass occurrence (1-based) to pick one`,"ambiguous_match",{statusCode:409,details:{matchCount:s.length}});i=1}if(!Number.isInteger(i)||i<1||i>s.length)throw new y(`occurrence must be an integer from 1 to ${s.length}`,"invalid_occurrence",{details:{matchCount:s.length}});let d=s[i-1];if(d===void 0)throw new y(`occurrence must be an integer from 1 to ${s.length}`,"invalid_occurrence",{details:{matchCount:s.length}});return e.slice(0,d)+r+e.slice(d+n.length)}function Gu(e){let t=[];for(let n of e)if(n.op==="add"){let r=n.node;if(typeof r!="object"||r===null||Array.isArray(r)){t.push({code:"invalid_node",error:`add op needs node as an object (got ${Zi(r)})`});continue}let o=r;(!o.id||typeof o.id!="string")&&t.push({code:"missing_node_id",error:"add op needs node.id"});try{Qe(o.type,"node.type")}catch(s){s instanceof y&&t.push({code:s.code,error:s.message,nodeId:o.id})}"wires"in o&&zt(o.wires,t,typeof o.id=="string"?o.id:void 0)}else n.op==="set"&&typeof n.set=="object"&&"wires"in n.set&&zt(n.set.wires,t,n.nodeId);return t}function Ku(e){let t=new Set;for(let n of e)n.op==="add"&&typeof n.node.id=="string"?t.add(n.node.id):n.op!=="add"&&typeof n.nodeId=="string"&&t.add(n.nodeId);return t}async function vt(e,t,n,r={}){if(!t)throw new y("flow id is required","missing_flow_id");if(!n.length)throw new y("At least one node edit is required","missing_edits");let o=Gu(n);o.push(...jo([n.flatMap(h=>h.op==="add"?[h.node]:[])],"the batch (add ops)")),qe(o);let s=await e.getFlow(t);if(r.expectedRev!==void 0&&s.rev!==r.expectedRev){let h=Array.isArray(s.flow.nodes)?[...s.flow.nodes]:[];return{expectedRev:s.rev,flow:{...s.flow,id:s.flow.id,type:s.flow.type??"tab",nodes:h}}}let i=Array.isArray(s.flow.nodes)?[...s.flow.nodes]:[],d=Array.isArray(s.flow.configs)?[...s.flow.configs]:[],l={...s.flow,id:s.flow.id,type:s.flow.type??"tab",nodes:i,configs:d},c=i.map(h=>({...h})),u=[];for(let h of n)try{h.op==="add"&&r.nodeTypes&&await yi(r.nodeTypes,h.node.type),Vi(c,t,h)}catch(g){if(g instanceof y)u.push({code:g.code,error:g.message,...g.statusCode!==400?{statusCode:g.statusCode}:{},...g.details??{}});else throw g}u.push(...Uo(c,t));let p=Ku(n),f=await Ho(c,r.nodeTypes,{softWarningNodeIds:p});u.push(...f.errors),qe(u);for(let h of n)Vi(i,t,h);let m=[...f.warnings];if(r.nodeTypes){let h=await Bo(i,d,r.nodeTypes);m.push(...h.filter(g=>!g.nodeId||p.has(g.nodeId)))}return{expectedRev:s.rev,flow:l,...m.length>0?{warnings:m}:{}}}function Ji(e,t,n){if(!t||typeof t!="string")throw new y("Each edit needs a node id","missing_node_id");let r=e.findIndex(o=>o.id===t);if(r<0||!e[r])throw new y(`Node '${t}' was not found in flow '${n}'`,"node_not_found",{statusCode:404});return r}function Vi(e,t,n){if(n.op==="add"){let i=n.node;if(!i.id||typeof i.id!="string")throw new y("add op needs node.id","missing_node_id");if(Qe(i.type,"node.type"),e.some(d=>d.id===i.id))throw new y(`Node '${i.id}' already exists in flow '${t}'`,"node_already_exists");e.push({...i,id:i.id,type:i.type,z:typeof i.z=="string"&&i.z?i.z:t});return}if(n.op==="remove"){let i=Ji(e,n.nodeId,t);e.splice(i,1);return}let r=Ji(e,n.nodeId,t),o=e[r];if(!o)throw new y(`Node '${n.nodeId}' was not found in flow '${t}'`,"node_not_found",{statusCode:404});if(n.op==="replace_text"){if(!(n.field in o))throw new y(`Field '${n.field}' was not found on the node`,"field_not_found");let i=ea(o[n.field],n.field,n.find,n.replace,n.occurrence);e[r]={...o,id:o.id,type:o.type,[n.field]:i};return}let s={...o,id:o.id,type:o.type};for(let[i,d]of Object.entries(n.set)){if(Xi.has(i))throw new y(`Cannot change protected node property '${i}' via GET propose`,"protected_property");if(i==="wires"){let l=[];zt(d,l,n.nodeId),qe(l)}s[i]=d}e[r]=s}function Yi(e){if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean"||typeof e=="bigint")return String(e)}function es(e){let t={},n=e.set;if(n&&typeof n=="object"&&!Array.isArray(n))for(let[r,o]of Object.entries(n)){let s=Yi(o);s!==void 0&&(t[r]=s)}for(let[r,o]of Object.entries(e)){if(!r.startsWith("set.")||r.length<=4)continue;let s=r.slice(4);if(typeof o=="string")t[s]=o;else if(Array.isArray(o)&&typeof o[0]=="string")t[s]=o[0];else{let i=Yi(o);i!==void 0&&(t[s]=i)}}return t}function Hn(e){let t;try{t=Buffer.from(e,"base64url").toString("utf8")}catch{throw new y("patch must be base64url-encoded JSON","invalid_patch")}try{return JSON.parse(t)}catch{throw new y("patch JSON is malformed","invalid_patch_json")}}function Ju(e,t){if(typeof e.flowId=="string"&&e.flowId)return e.flowId;let n=e.flow;if(n&&typeof n=="object"&&!Array.isArray(n)){let r=n.id;if(typeof r=="string"&&r)return r}return t}function ts(e){return typeof e.id=="string"&&e.id?e.id:typeof e.nodeId=="string"&&e.nodeId?e.nodeId:typeof e.node=="string"&&e.node?e.node:""}function Vu(e){if(e==null||e==="")return;let t;if(typeof e=="number")t=e;else if(typeof e=="string")t=Number(e);else if(typeof e=="boolean"||typeof e=="bigint")t=Number(e);else throw new y("occurrence must be a positive integer","invalid_occurrence");if(!Number.isFinite(t))throw new y("occurrence must be a positive integer","invalid_occurrence");return Math.trunc(t)}function Yu(e){let t=e.node;if(t!==void 0)return Wu(t),{op:"add",node:t};let n={...e};return delete n.op,{op:"add",node:n}}function Xu(e){let t=ts(e);if(!t)throw new y("replace_text op needs node id","missing_node_id");let n=typeof e.field=="string"?e.field:"";if(!n)throw new y("replace_text op needs field","missing_field");if(typeof e.find!="string")throw new y("replace_text op needs find string","missing_find");if(typeof e.replace!="string")throw new y("replace_text op needs replace string","invalid_replace");let r=Vu(e.occurrence),o={op:"replace_text",nodeId:t,field:n,find:e.find,replace:e.replace};return r!==void 0&&(o.occurrence=r),o}function Zu(e){let t=ts(e);if(!t)throw new y("Each patch.nodes entry needs id","missing_node_id");let n=e.set,r={};if(n&&typeof n=="object"&&!Array.isArray(n))Object.assign(r,n);else for(let[o,s]of Object.entries(e))o==="id"||o==="nodeId"||o==="node"||o==="set"||o==="op"||(r[o]=s);return{op:"set",nodeId:t,set:r}}function Qu(e){let t=typeof e.op=="string"?e.op.toLowerCase():"set";if(t==="add")return Yu(e);if(t==="remove"){let n=ts(e);if(!n)throw new y("remove op needs id","missing_node_id");return{op:"remove",nodeId:n}}if(t==="replace_text")return Xu(e);if(t!=="set")throw new y("nodes[].op must be 'add', 'remove', 'set', or 'replace_text'","invalid_patch");return Zu(e)}function Un(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw new y("patch must be a JSON object","invalid_patch");let n=e;if(n.type==="replace_flow"||n.flow&&typeof n.flow=="object"){let r=Ju(n,t);if(!r)throw new y("patch.replace_flow needs flowId","missing_flow_id");return{kind:"replace_flow",flowId:r,flow:n.flow}}if(Array.isArray(n.nodes)){let r=typeof n.flowId=="string"?n.flowId:t;if(!r)throw new y("patch.nodes needs flow id","missing_flow_id");let o=[];for(let i of n.nodes){if(!i||typeof i!="object"||Array.isArray(i))throw new y("Each patch.nodes entry must be an object","invalid_patch");o.push(Qu(i))}let s=o.filter(i=>i.op==="set").map(i=>({nodeId:i.nodeId,set:i.set}));return{kind:"nodes",flowId:r,ops:o,edits:s}}throw new y("patch must include nodes[] edits or type/flow for replace_flow","invalid_patch")}var Xi,Or=U(()=>{"use strict";J();qo();wt();Xi=new Set(["id","type","z"]);a(Zi,"describeNonObjectNode");a(Wu,"assertAddNodeObject");a(jn,"buildPatchedFlowTab");a(Qi,"findTextOccurrences");a(ea,"applyReplaceTextToValue");a(Gu,"collectOpShapeIssues");a(Ku,"touchedNodeIdsFromOps");a(vt,"applyFlowNodeOps");a(Ji,"requireNodeIndex");a(Vi,"applyOneNodeOp");a(Yi,"stringifyQueryValue");a(es,"collectSetProps");a(Hn,"decodeBase64UrlJson");a(Ju,"resolveReplaceFlowId");a(ts,"resolveNodeId");a(Vu,"parseOccurrence");a(Yu,"parseAddOp");a(Xu,"parseReplaceTextOp");a(Zu,"parseSetOp");a(Qu,"parseNodeOp");a(Un,"interpretPatch")});function Vt(e,t){return gt(e.createdBy)===t.id}function ns(e,t){return!t||t.length===0||t.includes(e)}function ep(e){return typeof e=="string"&&ra.has(e)}function Xt(e,t){let n=tp[e],r=e!=="status"&&Array.isArray(t);return new y(r?`Invalid ${e} filter: pass it at most once (multiple values are not supported). Allowed: ${n}`:`Invalid ${e} filter. Allowed: ${n}`,"invalid_filter",{details:{parameter:e,allowed:n,hint:`Pass ${e} as ${n}, or omit it.`}})}function $r(e,t){if(Array.isArray(t))throw Xt(e,t);return t}function np(e){return typeof e=="number"?Number.isInteger(e)?e:null:typeof e=="string"&&/^-?\d+$/.test(e.trim())?Number(e.trim()):null}function rs(e){return e==null||e===""}function rp(e){if(!rs(e)){if(e==="true"||e==="1"||e===!0)return!0;if(e==="false"||e==="0"||e===!1)return!1;throw Xt("mine",e)}}function os(e){if(rs(e))return;let t=(Array.isArray(e)?e:[e]).flatMap(r=>typeof r=="string"?r.split(","):[r]),n=[];for(let r of t){let o=typeof r=="string"?r.trim():r;if(o!==""){if(!ep(o))throw Xt("status",e);n.includes(o)||n.push(o)}}return n.length>0?n:void 0}function na(e,t,n,r){if(rs(t))return;let o=np(t);if(o===null||o<n||o>r)throw Xt(e,t);return o}function Mr(e){let t={},n=rp($r("mine",e.mine));n!==void 0&&(t.mine=n);let r=os(e.status);r&&(t.status=r);let o=$r("flowId",e.flowId??e.flow_id??e.flow);typeof o=="string"&&o.length>0&&(t.flowId=o);let s=na("limit",$r("limit",e.limit),1,Yt);s!==void 0&&(t.limit=s);let i=na("offset",$r("offset",e.offset),0,Number.MAX_SAFE_INTEGER);return i!==void 0&&(t.offset=i),t}function kt(e){let t=e.offset+e.returned,n=t<e.total;return{total:e.total,offset:e.offset,...e.limit!==null?{limit:e.limit}:{},hasMore:n,...n?{nextOffset:t}:{}}}function op(e,t){let n=e.length,r=t.offset!==void 0&&t.offset>0?t.offset:0;if(t.limit===void 0)return{items:r>0?e.slice(r):e,total:n,limit:null,offset:r};let o=Math.min(Math.max(1,t.limit),Yt);return{items:e.slice(r,r+o),total:n,limit:o,offset:r}}function Zt(e,t,n={},r={}){let o=r.mine===!0?t.id:void 0,s={};o&&(s.linkId=o),r.flowId&&(s.flowId=r.flowId);let d=e.list(s).filter(c=>r.mine!==!1||!Vt(c,t)).filter(c=>ns(c.status,r.status)).map(c=>({...ae(Jt(c),n),mine:Vt(c,t)})),l=op(d,r);return{proposals:l.items,total:l.total,limit:l.limit,offset:l.offset}}function ze(e,t,n,r={}){if(!n)throw new y("Missing proposal id","missing_proposal_id");let o=e.get(n);return{...ae(Jt(o),r),mine:Vt(o,t)}}function sp(e){let t={kind:"access-link",linkId:e.id};return typeof e.label=="string"&&e.label&&(t.label=e.label),t}function Qt(e,t,n,r,o,s={}){if(!r)throw new y("Missing proposal id","missing_proposal_id");let i;try{i=e.get(r)}catch(f){throw f instanceof se,f}if(!Vt(i,n))throw new Bt(r);if(i.status!=="pending")throw new ne(r,i.status);let d=typeof o=="string"&&o.trim()?o.replace(/\0/g,"").trim().slice(0,2e3):"Withdrawn by access link",l=sp(n),c=e.markWithdrawn(r,l,d),u=ae(Jt(c),s),p=u.flowLabel||void 0;return t.append("proposal.withdrawn",l,{proposalId:c.id,flowId:c.payload.flowId,title:u.title,displayName:u.displayName,reason:d,...p?{flowLabel:p}:{}}),{...u,mine:!0}}function ip(e,t,n,r={}){let o;try{o=e.get(t.id)}catch{return{}}if(r.liveRev){V(e,r.liveRev,{flowId:o.payload.flowId});try{o=e.get(t.id)}catch{return{}}}return o.status!=="pending"?{}:$e(Oe(e,o,n))}function ge(e,t,n,r={},o={}){let s="mine"in n?n:{...ae(n,r),mine:Vt(n,t)},i=0;if(o.liveRev)try{let c=e.get(s.id).payload.flowId;i=V(e,o.liveRev,{flowId:c});let u=e.get(s.id);s={...ae(u,r),mine:Vt(u,t)}}catch{}let d=he(Jt(s),r),l={...d,mine:s.mine,...Z(i)};return o.includeConflicts!==!1&&d.status==="pending"&&Object.assign(l,ip(e,d,t.id)),l}async function ye(e){let t=await e.getFlows();return{rev:t.rev,flows:t.flows}}async function _e(e,t){if(!t)throw new y("Missing flow id","missing_flow_id");let n=await e.getFlow(t);return{rev:n.rev,flow:qt(t,n.flow)}}function ap(e){if(typeof e.expectedRev=="string"&&e.expectedRev.length>0)return e.expectedRev;if(typeof e.expected_rev=="string"&&e.expected_rev.length>0)return e.expected_rev}function oa(e,t){if("nodes"in e&&e.nodes!==void 0&&!Array.isArray(e.nodes))throw new y("payload.nodes must be an array of node ops","invalid_nodes");if(t!==void 0&&!Array.isArray(t))throw new y("nodes must be an array of node ops","invalid_nodes")}async function dp(e,t,n,r,o){let{applyFlowNodeOps:s,interpretPatch:i}=await Promise.resolve().then(()=>(Or(),ta));oa(n,r);let d=Array.isArray(n.nodes)?n.nodes:Array.isArray(r)?r:null;if(!d)throw new y("payload.flow is required","missing_flow");let l=n.flowId,c=typeof l=="string"?l:"";if(!c)throw new y("payload.flowId is required","missing_flow_id");let u=i({nodes:d,flowId:c},c);if(u.kind!=="nodes")throw new y("payload.nodes must be node ops","invalid_patch");let p=await s(e,u.flowId,u.ops,{expectedRev:t,...o?{nodeTypes:o}:{}});if(p.expectedRev!==t)throw new K(t,p.expectedRev,"submit_proposal");return{flowId:u.flowId,flow:p.flow}}async function lp(e,t,n,r,o){let s=n.flowId,i=n.flow,d=!i||typeof i!="object"||Array.isArray(i);if(d&&oa(n,r),d&&(Array.isArray(n.nodes)||Array.isArray(r))){let l=await dp(e,t,n,r,o);s=l.flowId,i=l.flow}if(typeof s!="string"||s.length===0)throw new y("payload.flowId is required","missing_flow_id");if(!i||typeof i!="object"||Array.isArray(i))throw new y("payload.flow is required","missing_flow");return{flowId:s,flow:i}}function cp(e,t){try{return{safeExpectedRev:Fe(e.slice(0,200),"expectedRev"),safeFlowId:Fe(t.slice(0,200),"flowId")}}catch{throw new y("expectedRev and payload.flowId must be safe path segments","invalid_path_segment")}}function up(e,t){let n=Wi(ht(e),t);return n.unresolved.length>0&&qe(n.unresolved.map(r=>({code:"redacted_placeholder_mismatch",error:`Node ${r.nodeId} field ${r.property}: ${r.reason}. [REDACTED] placeholders are restored only when left exactly where they were shown (same node, field, env/header name, and unchanged line). Do not copy, move, or add placeholders; keep those lines unchanged or supply a real new value.`,nodeId:r.nodeId,property:r.property,statusCode:400}))),n}async function rt(e,t,n,r,o,s={},i){let d=r.type,l=ap(r),c=r.payload;if(d!=="replace_flow")throw new y("type must be 'replace_flow'","invalid_proposal_type");if(!l)throw new y("expectedRev is required (top-level field next to type and payload)","missing_expected_rev");if(!c||typeof c!="object"||Array.isArray(c))throw new y("payload must be an object","invalid_payload");let u=c,p=qi(o),f=await lp(bt(p),l,u,r.nodes,i),{safeExpectedRev:m,safeFlowId:h}=cp(l,f.flowId),g=f.flow,k=Buffer.byteLength(JSON.stringify(c),"utf8");if(k>H)throw new Y("proposal payload",k,H);qe(vi(g,h));let C=await p.getFlows();if(C.rev!==m)throw new K(m,C.rev,"submit_proposal");let L=up(g,C.flows),N=ht(L.flow);qe(bi(N,h,C.flows));let Ue=await p.getFlow(h);if(Ue.rev!==m)throw new K(m,Ue.rev,"submit_proposal");let Ce=ht(Ue.flow);i&&await gi(i,N);let{errors:S,warnings:b}=await ki(N,{flowId:h,baseFlow:Ce,...i?{nodeTypes:i}:{}});qe(S),b.unshift(...Ki(L.restoredFields,N,C.flows).map(le=>({...le})));let w={kind:"access-link",linkId:n.id};n.label!==void 0&&(w.label=n.label);let v=n.oauthClientId&&n.oauthUser?{kind:"oauth",clientId:n.oauthClientId,user:n.oauthUser}:w,A=Er(r.title)??Go(Ce,N),E=e.create({type:"replace_flow",expectedRev:m,payload:{flowId:h,flow:N},baseFlow:Ce,...L.restoredFields.length>0?{restoredSecrets:L.restoredFields}:{},title:A,createdBy:v,...b.length>0?{warnings:b}:{}}),$=ae(Jt(E),s),F=$.flowLabel||void 0;return t.append("proposal.created",w,{proposalId:E.id,flowId:h,expectedRev:m,title:$.title,displayName:$.displayName,...F?{flowLabel:F}:{},...b.length>0?{warningCount:b.length}:{},...L.restored>0?{restoredRedactions:L.restored}:{}}),$}function M(e,t="rest"){return Bn(e,t)}var Yt,ra,tp,y,J=U(()=>{"use strict";xe();Ar();Ze();yr();G();De();qo();wt();tt();Re();nt();a(Vt,"isMineProposal");Yt=200,ra=new Set(["pending","approved","rejected","failed","withdrawn","expired","superseded"]);a(ns,"matchesStatusFilter");a(ep,"isProposalStatus");tp={status:[...ra].join(", "),limit:`integer 1\u2013${Yt}`,offset:"integer \u2265 0",mine:"true, false, 1, 0",flowId:"one flow tab id"};a(Xt,"invalidFilterError");a($r,"singleQueryValue");a(np,"parseIntegerFilter");a(rs,"isUnset");a(rp,"parseMineFilter");a(os,"parseStatusFilter");a(na,"parseRangeFilter");a(Mr,"parseListProposalsQuery");a(kt,"listPageFields");a(op,"applyListPagination");a(Zt,"listAccessibleProposals");a(ze,"getAccessibleProposal");a(sp,"accessLinkActor");a(Qt,"withdrawMyProposal");a(ip,"conflictsForProposal");a(ge,"presentAccessibleProposalForApi");y=class extends Error{static{a(this,"AiOpsValidationError")}statusCode;code;details;constructor(t,n="validation_error",r={}){super(t),this.name="AiOpsValidationError",this.code=n,this.statusCode=r.statusCode??400,r.details&&(this.details=r.details)}};a(ye,"listFlows");a(_e,"getFlow");a(ap,"resolveExpectedRev");a(oa,"assertNodesArrayOrAbsent");a(dp,"materializePayloadFromNodes");a(lp,"resolveReplaceFlowPayload");a(cp,"assertSafeRevAndFlowId");a(up,"strictRestoreOrThrow");a(rt,"submitReplaceFlowProposal");a(M,"aiOpsErrorToHttp")});function ss(e){return e==="get_link"?86400:2592e3}function is(e,t){return e==="get_link"?`get_link ttlSeconds must be \u2264 ${t} (24 hours)`:`ttlSeconds must be \u2264 ${t} (30 days) for ${e} tokens`}var At,qn=U(()=>{"use strict";a(ss,"maxTtlSecondsForKind");a(is,"ttlCapErrorMessage");At="ttlSeconds must be a positive number of seconds within the cap for the token kind: get_link \u2264 86400 (24h); rest and mcp \u2264 2592000 (30 days)."});function x(e,t,n,r,o=oe,s="error"){return{code:e,status:t,meaning:n,hint:r,channels:o,kind:s}}function zn(e){return fp.get(e)?.hint}function sa(e){return e.replace(/\\/g,"\\\\").replace(/\|/g,"\\|")}function ia(){let e=a(s=>`| \`${s.code}\` | ${s.status} | ${sa(s.meaning)} | ${sa(s.hint)} | ${s.channels.join(", ")} |`,"row"),t=["| Code | HTTP | Meaning | Next step | Channels |","| --- | --- | --- | --- | --- |"],n=Dr.filter(s=>s.kind==="error"),r=Dr.filter(s=>s.kind==="warning"),o=Dr.filter(s=>s.kind==="status");return[...t,...n.map(e),"","**Warnings** (in `warnings[]` on a successful propose; never blocking):","",...t,...r.map(e),"","**Auto-closed proposal statuses.** Pending proposals are closed automatically: `expired` when the live flow rev moves past their `expectedRev`, `superseded` when another proposal on the same flow is approved. Submit, get, and list responses include `autoClosedCount` when that request auto-closed proposals (omitted when zero).","",...t,...o.map(e)].join(`
|
|
10
|
-
`)}var oe,en,as,pp,tn,Dr,fp,jr=U(()=>{"use strict";qn();oe=["GET","REST","MCP"],en=["GET"],as=["REST","MCP"],pp=["GET","REST"],tn=["admin"];a(x,"e");Dr=[x("access_link_missing",401,"No Bearer / path token","Ask the user for a new access link of the right kind"),x("bearer_scheme_required",401,"Auth used Basic (or non-Bearer)","Send `Authorization: Bearer <token>`",as),x("token_expired",401,"Known token past `expiresAt` (body includes `expiredAt` and `accessLinksUrl`)","Ask the user to create a new link on the `/ai-access` dashboard \u2192 Access tokens (`accessLinksUrl`)"),x("access_link_invalid",401,"Unknown, revoked, or cleared after restart","Ask the user for a new access link"),x("wrong_token_kind",403,"Token kind does not match this surface","Mint the matching kind on `/ai-access` \u2192 Access tokens; show `tellUser` to the human"),x("ai_features_disabled",403,"Plugin AI features off","Owner enables AI Access / `adminAuth`"),x("auth_required",401,"Admin session required","Sign in to the Node-RED editor",tn),x("insufficient_permissions",403,"Admin lacks required scope","Need `*` for link mint / approve",tn),x("invalid_link_kind",400,"Unknown `kind` on create","Use `get_link`, `rest`, or `mcp`",tn),x("invalid_ttl",400,"`ttlSeconds` is zero, negative, not a number, or over the cap for the kind",At,tn),x("access_link_limit",409,"Too many active links","Revoke an existing link first",tn),x("access_link_not_found",404,"Unknown link id on revoke","List links and use an existing id",tn),x("link_create_not_supported",403,"Attempted mint under a GET link","Admin mints via `POST /mcp/api/v1/links` only",en),x("rate_limited",429,"Per-IP or per-token limit","Wait `retryAfter` / `Retry-After`"),x("payload_too_large",413,"Body / proposal over ~1.5MB","Shrink content"),x("invalid_json",400,"Malformed JSON body","Fix Content-Type / body",as),x("unsupported_media_type",415,"POST body is not `application/json`","Send `Content-Type: application/json`",as),x("url_too_long",414,"GET URL over soft cap (~8KB)","Use chunked drafts or REST/MCP",en),x("not_found",404,"Unknown path or resource","Re-read docs / discovery"),x("missing_query",400,"Required search `q` missing","Pass `?q=`",pp),x("invalid_filter",400,"Invalid list filter (`status`, `limit`, `offset`, `mine`); body names `parameter` and `allowed`","Use an allowed value: status one of the proposal statuses; limit integer 1\u2013200; offset integer \u2265 0; mine true|false"),x("invalid_arguments",400,"MCP tool arguments failed schema validation; body names `parameter`","Fix the named argument to match the tool input schema (see tools/list)",["MCP"]),x("invalid_diff_format",400,"Unknown `format` on proposal diff","Use `format=summary` (default) or `format=full`"),x("confirm_required",400,"GET withdraw without `confirm=1`","Repeat the request with `confirm=1`",en),x("missing_expected_rev",400,"Proposal submitted without `expectedRev` / `rev`","Read the flow and pass its `rev`"),x("version_mismatch",409,"Stale `rev` / `expectedRev`","Re-read flows and resubmit"),x("proposal_not_found",404,"Unknown proposal id","List proposals and use an existing id"),x("proposal_not_pending",409,"Action needs a pending proposal","Only pending can withdraw / decide"),x("proposal_not_owned",403,"Withdraw of another token\u2019s proposal","Only the creating token can withdraw"),x("validation_failed",400,"Several validation errors at once (message `N validation errors`)","Fix every entry in errors[] and resubmit."),x("duplicate_node_id",400,"The same node id appears more than once in a full tab or batch","Give every node a unique id"),x("missing_node_id",400,"Node / op without an id","Include a non-empty string id"),x("missing_node_type",400,"Node without a `type`","Set `type` to an installed palette type name"),x("invalid_node_type",400,"`type` is not a string","Use the palette type name exactly"),x("invalid_nodes",400,"`flow.nodes` / `flow.configs` not an array","Send arrays (use `[]` to clear)"),x("invalid_node",400,"Node entry is not an object","Send `{ id, type, \u2026 }` objects"),x("invalid_wires",400,"Bad wires shape, missing target, or more ports than outputs","Wire only to existing node ids / ports"),x("z_mismatch",400,"Node `z` differs from the flow id","Set `z` to the tab id or omit it"),x("node_already_exists",400,"`add` op for an id already in the flow","Use `set` or a new id"),x("protected_property",400,"Tried to change id / type / z","Omit id, type, and z from edits"),x("node_not_found",404,"Unknown node (single error only; several errors \u2192 `validation_failed`)","Re-read the flow"),x("field_not_found",404,"Unknown field on a node","Re-read the node"),x("text_not_found",400,"`replace_text` find had no match","Re-read field; find is exact"),x("ambiguous_match",409,"`replace_text` matched more than once","Pass `occurrence` (1-based) or unique find"),x("unknown_node_type",400,"Proposal uses a type that is not installed","List node-types / palette search; ask user to install"),x("node_type_not_found",404,"Unknown type on node-types detail","List types or search palette"),x("chunk_conflict",409,"Same draft seq re-sent with different data","Re-send identical data or start a new draft",en),x("missing_chunks",400,"Draft submit with gaps in seq","Upload every seq from 0 through max",en),x("draft_hash_mismatch",400,"Draft sha256 does not match assembled bytes","Send the hex sha256 of the assembled UTF-8",en),x("missing_config_node",201,"Warning: node has no (or an unresolved) config node yet","Configure before deploy if needed; see `candidates`",oe,"warning"),x("unwired_node",201,"Warning: node has outputs but no wires","Wire it, or ignore while scaffolding",oe,"warning"),x("incomplete_wires",201,"Warning: fewer wire ports than outputs","Add ports if outputs should connect",oe,"warning"),x("redacted_placeholder_mismatch",400,"A `[REDACTED]` placeholder was added, duplicated, moved, or its env/header name changed","Keep placeholders exactly where they were shown (same line, same name) or supply a real value",oe),x("secret_destination_changed",201,"Warning: a node holding a restored secret changed where it sends data (url/host/method/headers/\u2026)","Reviewer must verify the new destination before approving",oe,"warning"),x("secret_node_code_changed",201,"Warning: a node holding a restored secret had its code changed","Reviewer must check the code does not send the secret elsewhere",oe,"warning"),x("secret_wires_changed",201,"Warning: a node holding a restored secret had its output wires changed","Reviewer must check where its messages now go",oe,"warning"),x("secret_scope_changed",201,"Warning: tab/group env or a config node scoping a secret-holding node changed","Reviewer must check env-resolved hosts/URLs (e.g. `${API_HOST}`)",oe,"warning"),x("outbound_node_changed",201,"Warning: proposal adds/changes an outbound-capable node (http request/response, mqtt, tcp, udp, email, websocket, link out/call, function, exec, file, or any node with url/host fields)","Reviewer must check it cannot send secret data elsewhere",oe,"warning"),x("secret_tab_changed",201,"Warning: proposal with restored secrets also changes other nodes in the tab","Review every change before approving",oe,"warning"),x("secret_reference_added",201,"Warning: an added/changed node now reads a secret-named env/global/flow value (env.get, $env, ${\u2026}, typed input)","Reviewer must check the value is not sent anywhere unexpected",oe,"warning"),x("expired",200,"Status: pending proposal auto-closed because the live flow rev moved past `expectedRev`","Re-read the flow and submit a new proposal",oe,"status"),x("superseded",200,"Status: pending proposal auto-closed because another proposal on the same flow was approved","Re-read the flow and submit a new proposal if still needed",oe,"status"),x("upstream_error",502,"Node-RED runtime/flows error","Retry; check Node-RED logs"),x("internal_error",500,"Unexpected server error","Retry; check Node-RED logs")],fp=new Map(Dr.map(e=>[e.code,e]));a(zn,"registryHint");a(sa,"cell");a(ia,"renderGetLinkErrorTable")});function de(e,t,n={}){return ds(e,t,n)}function mp(e,t){return t==="excess_ports"?e==="mcp"?"This node has more wire ports than its type declares \u2014 remove extra ports or raise the node outputs, then retry.":e==="rest"?"This node has more wire ports than its type declares \u2014 trim wires[] or raise outputs, then retry.":"This node has more wire ports than its type declares \u2014 trim wires or raise outputs, then retry.":t==="missing_target"?e==="mcp"?"Wire targets must be node ids in this flow \u2014 add the node in the same patch or use get_flow ids.":e==="rest"?"Wire targets must be node ids in this flow \u2014 add the node in the same patch or use GET /flows/{id} ids.":"wires must list existing node ids (or add those nodes in the same patch).":e==="mcp"||e==="rest"?"Fix wires: targets must exist in this flow, and port count must not exceed the node outputs.":"wires must list existing node ids (or add those nodes in the same patch)."}function hp(e){return e==="mcp"?"Call list_node_types or search_palette. Ask the user to install via Manage palette, then recheck. This API never installs.":e==="rest"?"Call GET /node-types?q= or GET /palette/search?q=. Ask the user to install via Manage palette, then recheck. This API never installs.":"Call GET \u2026/node-types?q= or \u2026/palette/search?q=. Ask the user to install via Manage palette, then recheck. This API never installs."}function ot(e,t,n,r){return e==="mcp"?t:e==="rest"?n:r}function ds(e,t,n={}){switch(e){case"version_mismatch":return ot(t,"Re-read with list_flows/get_flow and retry with the current rev.","Re-read the flow and retry with the current rev.","Re-read the flow and retry with the current rev.");case"node_not_found":return ot(t,"Call get_flow or get_node and use a node id from that response.","Call GET /flows/{id} and use a node id from that response.","Call GET \u2026/flows/<id> and use a node id from that response.");case"flow_not_found":return ot(t,"Call list_flows to list valid ids.","Call GET /flows to list valid ids.","Use an id from GET ./flows.");case"proposal_not_found":return ot(t,"Call list_proposals and use an id from that response.","List proposals and use an id from that response.","List proposals and use an id from that response.");case"proposal_not_owned":return"Only the access link that created a pending proposal can withdraw it.";case"proposal_not_pending":return"Only pending proposals can be withdrawn or decided.";case"confirm_required":return"Add confirm=1 to the withdraw URL (required so link prefetchers cannot withdraw).";case"invalid_wires":return mp(t,n.cause);case"node_type_not_found":case"unknown_node_type":return hp(t);case"missing_node_type":return'Each add op needs node.type as a non-empty string (e.g. "inject").';case"invalid_line_range":return ot(t,'Pass lines as "A" or "A-B" (1-based inclusive). Reversed ranges (end < start) are invalid.',"Use lines=A or lines=A-B (1-based inclusive). Reversed ranges (end < start) are invalid.","Use lines=A or lines=A-B (1-based inclusive). Reversed ranges (end < start) are invalid.");case"line_range_out_of_bounds":return"start is past the end of the field \u2014 check totalLines and request a lower lines range.";case"ambiguous_match":return"find matched more than once \u2014 pass occurrence=N (1-based) or a more unique find string.";case"missing_expected_rev":return ot(t,"Pass expectedRev from list_flows/get_flow (top-level, next to type and payload).","Pass expectedRev from GET /flows or GET /flows/{id} (top-level, next to type and payload).","Pass rev from GET \u2026/flows or \u2026/flows/<id>.");case"missing_query":return ot(t,"Pass a non-empty q string.","q query parameter is required.","q query parameter is required.");case"unsupported_media_type":return"Send Content-Type: application/json with a JSON body.";case"text_not_found":return"Re-read the node field; find must match exactly (case-sensitive).";case"field_not_found":return"Re-read the node; field must be an existing string property (template, func, \u2026).";case"field_not_string":return ot(t,"This tool only returns string fields (template, func, \u2026). Use get_node for other property types.","This endpoint only returns string fields (template, func, \u2026). Use GET /nodes/{id} for other property types.","This endpoint only returns string fields (template, func, \u2026). Use GET /nodes/{id} for other property types.");default:return}}function Bn(e,t,n={}){let r=n.canonicalizeMessage!==!1,o=null;if(e instanceof y?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,...e.details??{}}}:e instanceof K?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,expectedRev:e.expectedRev,currentRev:e.currentRev}}:e instanceof W?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,flowId:e.flowId}}:e instanceof se||e instanceof Bt||e instanceof ne||e instanceof Y?o={statusCode:e.statusCode,body:{error:e.message,code:e.code}}:(e instanceof Ne||e instanceof Se)&&(o={statusCode:e.statusCode,body:{error:e.message,code:e.code}}),!o)return null;let s=o.body.code;if(r&&!Array.isArray(o.body.errors)){let i=je[s];i&&(o.body.error=i)}if(typeof o.body.hint!="string"||!o.body.hint){let i=ds(s,t,{cause:o.body.cause})??zn(s);i&&(o.body.hint=i)}return o}function aa(e,t,n={}){let o={error:n.error??je[t]??"Request failed",code:t,...n.details??{}},s=ds(t,e,{cause:o.cause})??zn(t);return s&&(o.hint=s),{statusCode:n.statusCode??400,body:o}}var je,De=U(()=>{"use strict";xe();Ze();G();J();jr();je={node_not_found:"Node was not found",flow_not_found:"Flow was not found",field_not_found:"Field was not found",field_not_string:"Field is not a string",missing_query:"q query parameter is required",missing_flow_id:"Missing flow id",missing_node_id:"Missing node id",missing_field:"Missing field name",invalid_line_range:"lines must be A or A-B (1-based inclusive; end >= start)",line_range_out_of_bounds:"lines range is out of bounds",proposal_not_found:"Proposal was not found",proposal_not_owned:"Proposal was not created by this access link",proposal_not_pending:"Proposal is not pending",confirm_required:"Withdraw over GET requires confirm=1",unsupported_media_type:"Content-Type must be application/json",upstream_error:"Upstream request failed",internal_error:"Internal server error"};a(de,"channelErrorHint");a(mp,"invalidWiresHint");a(hp,"nodeTypeHint");a(ot,"pickChannel");a(ds,"hintFor");a(Bn,"mapErrorForChannel");a(aa,"buildChannelError")});var og={};Os(og,{ACCESS_LINK_INVALID_MESSAGE:()=>ut,ACCESS_LINK_MISSING_MESSAGE:()=>me,AccessLinkStore:()=>Ct,EDITOR_SIDEBAR_ASSET_VERSION:()=>on,MAX_PROPOSAL_JSON_BYTES:()=>H,SettingsStore:()=>Mt,acceptedMediaTypes:()=>wl,areAiFeaturesEnabled:()=>ke,bindSettingsStore:()=>cr,createPluginServices:()=>bo,default:()=>rg,getStorageDir:()=>co,isAdminAuthEnabled:()=>z,isMcpStreamRequest:()=>bl,packageVersion:()=>rn,registerRoutes:()=>vl,renderEditorSidebarJs:()=>Hr,renderEditorSidebarLoaderHtml:()=>ua,requireAccessLink:()=>Ut,resolveAiAccessSettings:()=>ve});module.exports=Rl(og);var yl=require("fs");var tr=require("fs"),bn=require("path");G();function nr(e){let t=(0,bn.join)(e,".ai-access");return{root:t,proposals:(0,bn.join)(t,"proposals"),logs:(0,bn.join)(t,"logs"),settings:(0,bn.join)(t,"config.json")}}a(nr,"resolveAiAccessPaths");function Ao(e){(0,tr.mkdirSync)(e,{recursive:!0,mode:ko});try{(0,tr.chmodSync)(e,ko)}catch{}}a(Ao,"mkdirSecure");function Hs(e){Ao(e.root),Ao(e.proposals),Ao(e.logs)}a(Hs,"ensureAiAccessDirs");pt();Le();function mr(e){let t=pe(e);return`auth-tokens${(t==="/"?"":t).replace(/\//g,"-")}`}a(mr,"editorAuthTokenKey");Le();De();function gp(e){if(!e)return!1;let t=e.split(";")[0]?.trim().toLowerCase()??"";return t==="application/json"||t.endsWith("+json")}a(gp,"isJsonContentType");function yp(e){let t=e.headers["content-length"];if(typeof t=="string"&&t.length>0){let n=Number(t);if(Number.isFinite(n)&&n>0)return!0;if(n===0)return!1}return e.headers["transfer-encoding"]!==void 0}a(yp,"hasRequestBody");function We(e="rest"){return(t,n,r)=>{let o=t.headers["content-type"];if(gp(o)){r();return}if(!o&&!yp(t)){r();return}let s=aa(e,"unsupported_media_type",{statusCode:415});n.status(s.statusCode).json(s.body)}}a(We,"requireJsonContentType");var wp="text/plain; charset=utf-8",bp="text/html; charset=utf-8",vp="default-src 'none'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'";function kp(e){let t=e.query?.format;return typeof t=="string"?t.toLowerCase():void 0}a(kp,"formatParam");function Ap(e){let t=kp(e);if(t==="md"||t==="json")return"native";if(!e.get("accept"))return"html";let n=e.accepts(["text/html","application/json","text/markdown","text/plain"]);return n==="application/json"||n==="text/markdown"?"native":n==="text/plain"?"plain":"html"}a(Ap,"chooseGetLinkRepresentation");function da(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}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("<",n+t.length))return t;let r=t.replace(/[.]+$/,""),o=t.slice(r.length);return r.length<=2||r.includes("..")||r.includes("//")||r.includes("&")?t:`<a href="${r}">${r}</a>${o}`})}a(xp,"linkifyEscaped");function Tp(e,t){return`<!doctype html>
|
|
11
|
-
<html><head><meta charset="utf-8">${t?`<base href="${
|
|
12
|
-
`}a(
|
|
9
|
+
`);n.push({before:i>=0?o.slice(i+1):o,after:d>=0?s.slice(0,d):s})}return n}function go(e){return JSON.stringify([e.before,e.after])}function ho(e){let t=new Map;return ur(e.text).forEach((n,r)=>{let o=go(n),s=t.get(o)??[];s.push(r),t.set(o,s)}),t}function Qa(e,t,n,r){if(e===t)return{text:n,restored:r.filter(f=>f.kind==="secret").length};let o=ur(e),s=ur(t);if(o.length>s.length)return{error:`contains ${o.length} [REDACTED] placeholders but the original had ${s.length}`};let i=ho({text:t,occurrences:r}),d=new Set([...i].filter(([,f])=>new Set(f.map(h=>r[h]?.kind)).size>1).map(([f])=>f)),l=new Map,c=e.split(J),u=c[0]??"",p=0;for(let f=0;f<o.length;f++){let h=o[f];if(!h)break;let m=go(h),g=i.get(m)??[];if(d.has(m))return{error:"ambiguous literal and secret [REDACTED] occurrences"};let y=l.get(m)??0,A=g[y]??-1;if(A<0)return{error:"a [REDACTED] placeholder is not on an unchanged original line"};l.set(m,y+1);let x=r[A];x?.kind==="secret"&&p++,u+=(x?.value??J)+(c[f+1]??"")}return{text:u,restored:p}}function vt(e){if(Z(e))for(let t of sf){let n=e[t];if(typeof n=="string"&&n.length>0)return`${t}=${n}`}}function fr(e,t){let n=a(s=>[s,...s.nodes??[],...s.configs??[]],"entities"),r=new Map(n(e).map(s=>[s.id,s])),o=[];for(let s of n(t)){let i=r.get(s.id);if(JSON.stringify(i?.env)===JSON.stringify(s.env))continue;let d=a(l=>{let c=new Set;return Array.isArray(l)?l.some(u=>!Z(u)||typeof u.name!="string"||!u.name?!1:c.has(u.name)?!0:(c.add(u.name),!1)):!1},"hasDuplicates");!d(i?.env)&&!d(s.env)||o.push({code:"duplicate_env_keys",...s.id?{nodeId:s.id}:{},property:"env",error:"Duplicate env names make pairing by name ambiguous when entries are reordered. Review the env values before approval."})}return o}function Qs(e,t,n,r=[]){let o=a(c=>{let u=new Map;return yo(c,{matcher:kn(dt()),count:0,fields:u}),u},"fieldsFor"),s=o(e),i=o(t),d=o(n),l=a((c,u,p,f,h=f,m=f)=>{if(typeof u=="string"){let g=s.get(`${e.id}\0${f}`);if(!g?.occurrences.some(E=>E.kind==="secret"))return u;let y=i.get(`${t.id}\0${h}`),A=d.get(`${n.id}\0${m}`),x=ur(g.text),$=ho(g),L=y?ho(y):new Map,D=A?ho(A):new Map,H=y?.occurrences.map(E=>E.value)??[],T=y?ur(y.text):[],k=y?.occurrences.some((E,v)=>{let S=T[v];return E.kind==="secret"&&S&&!$.has(go(S))}),_=!1;return g.occurrences.forEach((E,v)=>{if(E.kind!=="secret")return;let S=x[v],N=S?go(S):"",w=$.get(N)??[],F=L.get(N)??[],I=D.get(N)??[];if(w.length>1&&F.length>0&&F.length<w.length&&JSON.stringify(w.map(Bn=>g.occurrences[Bn]?.value))!==JSON.stringify(I.map(Bn=>A?.occurrences[Bn]?.value)))throw new pr;let C=a((Bn,Jc)=>{let Gi=Bn.get(N)??[],[fs]=Gi;return Gi.length===1&&fs!==void 0&&Jc?.occurrences[fs]?.kind==="secret"?fs:void 0},"matches"),ee=C($,g),ot=C(L,y),Un=C(D,A);if(!L.has(N)&&!k)return;if(ee===void 0||ot===void 0||Un===void 0){_=!0;return}let Wi=A?.occurrences[Un];y?.occurrences[ot]?.value===E.value&&Wi&&(H[ot]=Wi.value)}),_&&r.push({code:"secret_may_revert",nodeId:e.id,property:h,error:"Secret spans could not be aligned reliably. The proposed value was kept and may revert an editor secret change. Review this field before approval."}),!y||_?u:y.text.split(J).reduce((E,v,S)=>E+(S?H[S-1]??J:"")+v,"")}if(Array.isArray(u)){let g=Array.isArray(c)?c:[],y=Array.isArray(p)?p:[],A=new Set,x=new Set;return u.map(($,L)=>{let D=vt($),H=a((_,E)=>{let v=D===void 0?L:_.findIndex((S,N)=>!E.has(N)&&vt(S)===D);return E.add(v),v},"match"),T=H(g,A),k=H(y,x);return l(g[T],$,y[k],`${f}[${T}]`,`${h}[${L}]`,`${m}[${k}]`)})}if(Z(u)){let g=Z(c)?c:{},y=Z(p)?p:{},A=a((x,$)=>x?`${x}.${$}`:$,"path");return Object.fromEntries(Object.entries(u).map(([x,$])=>[x,l(g[x],$,y[x],A(f,x),A(h,x),A(m,x))]).filter(([,x])=>x!==void 0))}return u},"walk");return l(e,t,n,"")}function ed(e,t){let n=Vs(t,[]);return"error"in Qa(e,t,t,n)}function mo(e,t,n,r,o,s){if(typeof e=="string"){if(!e.includes(J))return e;let i=s.fields.get(`${r}\0${o}`);if(typeof t=="string"&&typeof n=="string"&&(i||n.includes(J))){let d=Qa(e,t,n,i?.occurrences??Vs(n,[]));return"error"in d?(s.unresolved.push({nodeId:r,property:o,reason:d.error}),e):(d.restored>0&&(s.restored+=d.restored,s.restoredFields.push({nodeId:r,property:o})),d.text)}return s.unresolved.push({nodeId:r,property:o,reason:"no redacted secret exists at this node id and field"}),e}if(Array.isArray(e)){let i=Array.isArray(t)?t:[],d=Array.isArray(n)?n:[],l=new Set;return e.map(c=>{let u=Z(c)&&c.redacted===!0?Js(c,"redacted"):c,p=vt(u),f=-1;if(p!==void 0){let h=d.map((m,g)=>vt(m)===p&&!l.has(g)?g:-1).filter(m=>m>=0);h.length>0&&(f=h[0]??-1)}return f>=0?(l.add(f),mo(u,i[f],d[f],r,`${o}[${f}]`,s)):mo(u,void 0,void 0,r,`${o}[?]`,s)})}if(Z(e)){let i=Z(t)?t:{},d=Z(n)?n:{},l={};for(let[c,u]of Object.entries(e))l[c]=mo(u,i[c],d[c],r,o?`${o}.${c}`:c,s);return l}return e}function Js(e,t){let n={...e};return delete n[t],n}function af(e){let t=Js(e,dr);return Array.isArray(t.env)&&(t.env=t.env.map(n=>!Z(n)||n.redacted!==!0?n:Js(n,"redacted"))),t}function td(e,t,n,r){let o=typeof e.id=="string"?e.id:"",s=o?t.get(o):void 0,i={},d=s?yo({...s,id:o},r):void 0;for(let[l,c]of Object.entries(e))if(l!==dr){if(Ya.has(l)&&Array.isArray(c)){i[l]=c.map(u=>Z(u)?td(u,t,n,r):u);continue}if(Ka.has(l)){i[l]=c;continue}i[l]=mo(c,d?.[l],s?.[l],o,l,n)}return af(i)}function nd(e,t,n=dt()){let r=new Map;for(let l of t)Z(l)&&typeof l.id=="string"&&r.set(l.id,l);let o=new Map,s={matcher:kn(n),count:0,fields:o},i={fields:o,restored:0,restoredFields:[],unresolved:[]};return{flow:td(e,r,i,s),restored:i.restored,restoredFields:i.restoredFields,unresolved:i.unresolved}}function uf(e){return typeof e.type=="string"&&lf.has(e.type)?!0:cf.some(t=>typeof e[t]=="string"&&e[t]!=="")}function Ga(e){let t=new Set,n=a((r,o)=>{if(!(o>12)){if(typeof r=="string"){if(r.length>Va)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(Z(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"&&pf.has(r[c]))&&typeof i=="string"&&t.add(i),n(i,o+1)}},"walk");return n(e,0),t.delete(""),t}function lr(e,t){return JSON.stringify(e)===JSON.stringify(t)}function rd(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]);for(let r of n)if(!(ff.has(r)||r==="nodes"||r==="configs")&&!lr(e[r],t[r]))return!0;return!1}function hf(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 mf(e,t){return e.filter(n=>n.nodeId===t).map(n=>n.property).join(", ")}function gf(e,t,n,r){let o=[],s=df.filter(d=>!lr(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=[...Ja].filter(d=>!lr(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(",")}),lr(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 yf(e,t,n,r,o,s){let i=[];for(let d of hf(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?!lr(l?.env,u?.env):l!==void 0&&u!==void 0&&rd(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 wf(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&&!rd(c,l))continue;e.includes(d)||i.push(d);let u=typeof l.type=="string"?l.type:"node";uf(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?Ga(c):new Set,f=[...Ga(l)].filter(h=>!p.has(h)&&o.matches(h));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 bf(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)Z(s)&&typeof s.id=="string"&&n.set(s.id,s)}return n}function od(e,t,n,r=dt()){let o=[...new Set(e.map(u=>u.nodeId))],s=String(t.id),i=new Map;for(let u of n)Z(u)&&typeof u.id=="string"&&i.set(u.id,u);let d=bf(t,s),l=[],c=new Set;for(let u of o){let p=i.get(u),f=d.get(u);!p||!f||(l.push(...gf(u,mf(e,u),p,f)),l.push(...yf(u,p,i,d,s,c)))}return l.push(...wf(o,i,d,s,kn(r))),l}var dr,Wp,Gp,Ws,Ka,Kp,Ja,Va,Ya,Ua,Ba,fo,qa,Yp,za,Gs,Zs,Wa,sf,pr,df,lf,cf,pf,ff,Pe=q(()=>{"use strict";un();ke();un();dr="_aiRedactions",Wp=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"]),Gp=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"]),Ws=new Set(["env","msg","flow","global","jsonata","bool","date","node","cred"]),Ka=new Set(["id","type","z","g","x","y","w","h","wires","inputs","outputs","links","scope","icon","color","style","d","l","in","out","rev"]),Kp=new Set(["name","label","info"]),Ja=new Set(["func","initialize","finalize"]),Va=256*1024,Ya=new Set(["nodes","configs","subflows"]),Ua=["name","p","keyValue","key","property","k"],Ba={value:"type",v:"vt",to:"tot",valueValue:"valueType"},fo=new Map;a(kn,"createSecretNameMatcher");a(dt,"currentSecretNamePatterns");a(cr,"isSecretLikeValue");qa=new WeakMap;a(Jp,"contentRules");a(Vp,"buildContentRules");Yp=["PASSWORD","PASSWD","PASSPHRASE","SECRET","CLIENT_SECRET","TOKEN","API_KEY","APIKEY","PRIVATE","CREDENTIAL","PWD","PW"];a(Xp,"shannonEntropy");a(Zp,"looksRandom");a(Qp,"unquotedRule");a(ef,"decodeURIComponentSafe");a(tf,"parseConnSegment");a(nf,"connectionStringSpans");za=new WeakMap;a(rf,"scanTextForSecrets");a(Vs,"classifyPlaceholders");a(Z,"isPlainObject");a(of,"isFlowEntity");Gs=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&&cr(t)||t.length>Va)return this.record(n,[t]),this.ctx.fields?.set(`${this.entityId}\0${n}`,{text:J,occurrences:[{kind:"secret",value:t}]}),J;let o=n.split(/[.[]/,1)[0]??"",s=rf(t,this.ctx.matcher,{code:Ja.has(o)});return this.ctx.fields&&s.text.includes(J)&&this.ctx.fields.set(`${this.entityId}\0${n}`,{text:s.text,occurrences:Vs(t,s.spans)}),s.secrets.length>0?(this.record(n,s.secrets),s.text):t}env(t,n){return t.map((r,o)=>{if(!Z(r))return r;let s=typeof r.type=="string"?r.type:"";if(s==="cred"||typeof r.value!="string"||Ws.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(Kp.has(r))return this.string(t,n,!1);let s=o?.[`${r}Type`],i=typeof s=="string"&&Ws.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)):Z(t)?this.object(t,n):t}object(t,n){let r=!1;for(let s of Ua){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 Ba&&typeof i=="string"){let l=Ba[s],c=l?t[l]:void 0,u=typeof c=="string"&&Ws.has(c);o[s]=this.string(i,d,!u);continue}if(Ua.includes(s)&&typeof i=="string"){o[s]=this.string(i,d,!1);continue}o[s]=this.value(i,d,s,t)}return o}};a(yo,"redactEntity");a(Ys,"redactValueInner");a(bt,"redactFlowData");a(Ks,"normalizeSecretArrays");a(Xa,"secretValuesChanged");a(Xs,"redactConflictLabels");a(lt,"redactProposalForAi");Zs=Symbol.for("ai-access.rawFlowAdapter"),Wa=new WeakMap;a(kt,"aiRedactingAdapter");a(Za,"isRedactingAdapter");a(Oe,"rawFlowAdapter");a(Xe,"adapterForRequest");a(wo,"countRedactionPlaceholders");a(ur,"placeholderContexts");a(go,"contextKey");a(ho,"indexOccurrences");a(Qa,"restoreString");sf=["name","keyValue","p","property","key","k"];a(vt,"stableElementKey");a(fr,"duplicateEnvWarnings");pr=class extends Error{static{a(this,"SecretAlignmentConflictError")}constructor(){super("A duplicate secret line was removed while its live values changed. Resolve this field against the live flow."),this.name="SecretAlignmentConflictError"}};a(Qs,"preserveLiveSecrets");a(ed,"hasNewLiteralPlaceholder");a(mo,"restoreWalk");a(Js,"withoutKey");a(af,"stripMarkers");a(td,"restoreEntity");a(nd,"restoreRedactedPlaceholders");df=["type","url","host","hostname","server","broker","port","path","method","headers","endpoint","uri","protocol","proxy","tls","to","ret"],lf=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"]),cf=["url","host","hostname","server","broker","endpoint","uri"];a(uf,"isOutboundNode");pf=new Set(["env","global","flow"]);a(Ga,"referencedNames");ff=new Set(["x","y","w","h","l"]);a(lr,"sameJson");a(rd,"nodeChanged");a(hf,"scopeIds");a(mf,"restoredFieldList");a(gf,"secretNodeWarnings");a(yf,"scopeWarnings");a(wf,"tabWarnings");a(bf,"proposedNodeMap");a(od,"secretChangeWarnings")});function mr(e){if(e===null||typeof e!="object")return JSON.stringify(e)??"undefined";if(Array.isArray(e))return`[${e.map(mr).join(",")}]`;let t=e;return`{${Object.keys(t).filter(r=>t[r]!==void 0).sort().map(r=>`${JSON.stringify(r)}:${mr(t[r])}`).join(",")}}`}function Af(e){return(0,ad.createHash)("sha256").update(mr(e)).digest("hex").slice(0,16)}function ye(e){return Array.isArray(e)?e.filter(t=>t&&typeof t=="object"&&typeof t.id=="string"):[]}function An(e){let t=new Map;for(let n of ye(e.nodes))t.set(n.id,{node:n,config:!1});for(let n of ye(e.configs))t.set(n.id,{node:n,config:!0});return t}function xt(e){let t={};for(let n of ni)e[n]!==void 0&&(t[n]=e[n]);return t}function hr(e,t){let n=new Set([...Object.keys(e??{}),...Object.keys(t??{})]),r=[];for(let o of n)vf.has(o)||mr(e?.[o])!==mr(t?.[o])&&r.push(o);return r.sort()}function sd(e){return e.length>0&&e.every(t=>ri.has(t))}function id(e,t){let n={...e};for(let r of ri)t[r]===void 0?delete n[r]:n[r]=t[r];return n}function At(e){if(!e)return{};let t={};return typeof e.type=="string"&&(t.nodeType=e.type),typeof e.name=="string"&&e.name&&(t.nodeName=e.name.slice(0,120)),t}function ld(e,t){let n=e.find(r=>r.id===t&&(r.type==="tab"||r.type==="subflow"));if(n)return{tab:n,nodes:e.filter(r=>r.id!==t&&r.z===t)}}function Wt(e,t,n){let r=ld(e,t);if(!r)return;let o=new Set(ye(n?.configs).map(c=>c.id)),s=new Set(ye(n?.nodes).map(c=>c.id)),i=[],d=[];for(let c of r.nodes)((o.has(c.id)?!0:s.has(c.id)?!1:c.type!=="group"&&c.x===void 0&&c.y===void 0&&c.wires===void 0)?d:i).push(c);let l={id:t,type:r.tab.type,...xt(r.tab),nodes:i};return d.length>0&&(l.configs=d),l}function bo(e,t){if(typeof e=="string")return e.includes(J)&&(typeof t!="string"||ed(e,t));if(Array.isArray(e)){let n=Array.isArray(t)?t:[],r=new Set;return e.some((o,s)=>{let i=vt(o),d=i===void 0?s:n.findIndex((l,c)=>!r.has(c)&&vt(l)===i);return r.add(d),bo(o,n[d])})}if(e&&typeof e=="object"){let n=t&&typeof t=="object"?t:{};return Object.entries(e).some(([r,o])=>bo(o,n[r]))}return!1}function cd(e){let{flowId:t,proposed:n,liveFlows:r,liveRev:o}=e,s=[],i=a(w=>{s.length<kf&&s.push(w)},"push"),d=ld(r,t);if(!d)return i({kind:"tab_deleted",nodeId:t,message:"The flow tab this proposal edits was deleted in the editor.",choices:[]}),{state:"conflict",rev:o,conflicts:s,liveChangedIds:[t]};let l=e.base??(e.baseRev===o?Wt(r,t,n):void 0);if(l===void 0)return i({kind:"base_unavailable",nodeId:t,message:"This proposal was stored before base snapshots existed and the flows changed since, so live edits cannot be told apart from the proposal. Ask the AI to resubmit, or apply the proposal over the live tab.",choices:["proposed"]}),{state:"conflict",rev:o,conflicts:s,liveChangedIds:[]};let c=Wt(r,t,n),u=An(l),p=An(c),f=An(n),h=[],m=xt(l),g=xt(d.tab),y=xt(n),A=hr(m,g),x=hr(m,y),$=y;A.length>0&&(h.push(t),x.length===0?$=g:i({kind:"tab_changed_both",nodeId:t,fields:[...new Set([...A,...x])].sort(),message:"Both the editor and the proposal changed the flow tab settings.",choices:["live","proposed"]})),$={...$};for(let w of ni)g[w]!==void 0&&$[w]===void 0&&($[w]=w==="env"?[]:dd[w]);let L=new Map,D=new Set([...u.keys(),...p.keys(),...f.keys()]),H=new Map;for(let w of r)w.id!==t&&w.z!==t&&H.set(w.id,w);for(let w of D){let F=u.get(w),I=p.get(w),C=f.get(w),ee=F&&I?hr(F.node,I.node):[],ot=F&&C?hr(F.node,C.node):[];(F&&(!I||ee.length>0)||!F&&I)&&h.push(w),C&&F&&H.has(w)?i({kind:"id_collision",nodeId:w,...At(C.node),message:"This node id now exists outside this flow tab.",choices:["live"]}):F&&I&&C?ot.length===0?L.set(w,I):ee.length===0?L.set(w,C):sd(ee)?L.set(w,{...C,node:id(C.node,I.node)}):sd(ot)&&!Xa(F.node,I.node)?L.set(w,{...I,node:id(I.node,C.node)}):i({kind:"node_changed_both",nodeId:w,...At(I.node),fields:[...new Set([...ee,...ot])].filter(Un=>!ri.has(Un)).sort(),message:"Both the editor and the proposal changed this node.",choices:["live","proposed"]}):F&&I&&!C?ee.length===0?L.set(w,null):i({kind:"node_deleted_in_proposal_modified_live",nodeId:w,...At(I.node),fields:ee,message:"The proposal deletes this node, but it was changed in the editor.",choices:["live","proposed"]}):F&&!I&&C?ot.length===0?L.set(w,null):i({kind:"node_deleted_live",nodeId:w,...At(C.node),fields:ot,message:"This node was deleted in the editor, but the proposal changes it.",choices:["live","proposed"]}):F&&!I&&!C?L.set(w,null):!F&&I&&C?i({kind:"id_collision",nodeId:w,...At(C.node),fields:hr(I.node,C.node),message:"The proposal adds a node whose id was also added in the editor.",choices:["live","proposed"]}):!F&&I&&!C?L.set(w,I):!F&&!I&&C&&(H.get(w)??(w===t?d.tab:void 0)?i({kind:"id_collision",nodeId:w,...At(C.node),message:"The proposal adds a node whose id now exists on another flow tab.",choices:["live"]}):L.set(w,C))}if(s.length>0)return{state:"conflict",rev:o,conflicts:s,liveChangedIds:ei(h)};let T=[],k=[],_=new Set,E=a(w=>{if(_.has(w))return;let F=L.get(w);_.add(w),F&&(F.config?k:T).push(ud(F.node))},"emit");for(let w of ye(n.nodes))E(w.id);for(let w of ye(n.configs))E(w.id);for(let w of ye(c.nodes))E(w.id);for(let w of ye(c.configs))E(w.id);let v={id:t,type:d.tab.type,...$,nodes:T};(k.length>0||Array.isArray(n.configs))&&(v.configs=k);let S=new Set([...T,...k].map(w=>w.id)),N=new Set(r.filter(w=>w.id!==t&&w.z!==t).map(w=>w.id));for(let w of[...T,...k]){let F=[];if(Array.isArray(w.wires)){for(let C of w.wires)if(Array.isArray(C))for(let ee of C)typeof ee=="string"&&!S.has(ee)&&F.push(ee)}if(typeof w.g=="string"&&w.g.length>0&&!S.has(w.g)&&F.push(w.g),w.type==="group"&&Array.isArray(w.nodes))for(let C of w.nodes)typeof C=="string"&&!S.has(C)&&F.push(C);if(Array.isArray(w.links))for(let C of w.links)typeof C=="string"&&!S.has(C)&&!N.has(C)&&F.push(C);let I=[...new Set(F)].sort();I.length>0&&i({kind:"dangling_reference",nodeId:w.id,...At(w),fields:I,message:"After merging, this node points to a node that no longer exists (deleted in the editor).",choices:["live"]}),bo(w,p.get(w.id)?.node)&&i({kind:"redacted_placeholder",nodeId:w.id,...At(w),message:"A [REDACTED] placeholder would be deployed over a real value. Ask the AI to resubmit.",choices:[]})}if(d.tab.type==="subflow"){let w=new Set;for(let F of[d.tab.in,d.tab.out])if(Array.isArray(F)){for(let I of F)if(Array.isArray(I?.wires))for(let C of I.wires)typeof C?.id=="string"&&!S.has(C.id)&&w.add(C.id)}w.size>0&&i({kind:"dangling_reference",nodeId:t,fields:[...w].sort(),message:"A subflow port points to a node that would no longer exist.",choices:[]})}return bo($,g)&&i({kind:"redacted_placeholder",nodeId:t,message:"A [REDACTED] placeholder would be deployed into the tab settings. Ask the AI to resubmit.",choices:[]}),s.length>0?{state:"conflict",rev:o,conflicts:s,liveChangedIds:ei(h)}:Sf(v,n,o,[],ei(h))}function Sf(e,t,n,r,o){let s=gr(e);return{state:s===gr(t)?"clean":"merged",rev:n,conflicts:r,merged:e,mergedHash:s,liveChangedIds:o}}function gr(e){return Af(xf(e))}function xf(e){let t=[...ye(e.nodes),...ye(e.configs)].map(ud).sort((n,r)=>n.id<r.id?-1:n.id>r.id?1:0);return{...dd,...xt(e),nodes:t}}function ud(e){if(!("z"in e))return e;let t={...e};return delete t.z,t}function ei(e){return[...new Set(e)]}function pd(e){let{flowId:t,nodeId:n,choice:r,liveFlows:o}=e,s=[],i=Wt(o,t,e.proposed);if(!i)throw new Error("flow tab deleted");if(e.base===void 0){if(r!=="proposed")throw new Error("invalid choice");return{base:St(i),proposed:St(e.proposed),warnings:s}}let d=St(e.base),l=St(e.proposed);if(r==="proposed"&&(s.push(...fr(d,l).filter(h=>h.nodeId===n)),s.push(...fr(i,l).filter(h=>h.nodeId===n&&!s.some(m=>m.nodeId===h.nodeId)))),n===t){let h=xt(i),m=r==="proposed"?Qs({id:t,type:i.type??"tab",...xt(d)},{id:t,type:i.type??"tab",...xt(l)},{id:t,type:i.type??"tab",...h},s):h;for(let g of ni)delete d[g],h[g]!==void 0&&(d[g]=St(h[g])),delete l[g],m[g]!==void 0&&(l[g]=St(m[g]));return{base:d,proposed:l,warnings:s}}let u=An(i).get(n),p=An(d).get(n),f=An(l).get(n);return r==="proposed"&&p&&f&&u&&ti(l,n,{...f,node:Qs(p.node,f.node,u.node,s)}),ti(d,n,u?{...u,node:St(u.node)}:void 0),r==="live"&&ti(l,n,u?{...u,node:St(u.node)}:void 0),{base:d,proposed:l,warnings:s}}function ti(e,t,n){let r=ye(e.nodes),o=ye(e.configs),s=o.some(l=>l.id===t),i=r.some(l=>l.id===t);if(e.nodes=r.filter(l=>l.id!==t),Array.isArray(e.configs)&&(e.configs=o.filter(l=>l.id!==t)),!n)return;s||!i&&n.config?e.configs=[...ye(e.configs),n.node]:e.nodes=[...e.nodes,n.node]}function St(e){return e===void 0?e:JSON.parse(JSON.stringify(e))}var ad,ni,ri,dd,vf,kf,yr=q(()=>{"use strict";ad=require("crypto");un();Pe();ni=["label","disabled","info","env"],ri=new Set(["x","y"]),dd={label:"",disabled:!1,info:"",env:[]},vf=new Set(["z"]),kf=200;a(mr,"stableStringify");a(Af,"hashTab");a(ye,"asNodes");a(An,"mapTab");a(xt,"tabProps");a(hr,"changedFields");a(sd,"positionOnly");a(id,"withPosition");a(At,"nodeLabel");a(ld,"liveTabFromFlows");a(Wt,"nestedLiveTab");a(bo,"containsNewPlaceholder");a(cd,"rebaseFlowTab");a(Sf,"finish");a(gr,"hashFlowTab");a(xf,"normalizeForHash");a(ud,"withoutZ");a(ei,"dedupe");a(pd,"applyConflictChoice");a(ti,"setNode");a(St,"clone")});function gd(e,t){md=e,vo=t??(()=>Date.now())}function yd(){let e=md();return(Number.isFinite(e)&&e>=1?e:fd)*hd}function Tf(e){return`Proposal expired: it stayed open longer than the proposal TTL (${e} day${e===1?"":"s"}). Ask the AI to resubmit if still needed.`}function ko(e,t=vo()){let n=Date.parse(e.createdAt);return Number.isFinite(n)&&t-n>yd()}function ne(e,t={}){let n=vo(),r=Math.round(yd()/hd),o=0;for(let s of e.list())if(U(s.status)&&!(t.flowId&&s.payload.flowId!==t.flowId)&&!e.isApproving(s.id)&&ko(s,n))try{e.markClosed(s.id,"expired",Tf(r)),o+=1}catch{}return o}function oi(e,t){return cd({flowId:e.payload.flowId,base:e.baseFlow,proposed:e.payload.flow,liveFlows:t.flows,liveRev:t.rev,baseRev:e.expectedRev})}function si(e,t=vo()){let n={rev:e.rev,state:e.state,conflicts:e.conflicts.map(r=>({...r,choices:[...r.choices]})),liveChangedCount:e.liveChangedIds.length,checkedAt:new Date(t).toISOString()};return e.mergedHash&&(n.mergedHash=e.mergedHash),n}function Ze(e,t,n,r={}){let o=oi(t,n);return{proposal:e.isApproving(t.id)&&!r.duringApprove?e.get(t.id):e.recordRebase(t.id,si(o)),result:o}}async function wr(e,t,n={}){let r=ne(e,n.flowId?{flowId:n.flowId}:{}),o=n.live;if(!o&&t)try{o=await Oe(t).getFlows()}catch{}if(!o)return{expired:r,conflicts:0,checked:0};let s=$e(e,o,n);return{...s,expired:r+s.expired}}function $e(e,t,n={}){let r={expired:0,conflicts:0,checked:0};for(let o of e.list())if(U(o.status)&&!(n.flowId&&o.payload.flowId!==n.flowId)&&!e.isApproving(o.id))try{if(ko(o)){r.expired+=ne(e,{flowId:o.payload.flowId});continue}let{proposal:s}=Ze(e,o,t);r.checked+=1,s.status==="conflict"&&(r.conflicts+=1)}catch{}return r}function wd(e,t=500){let n,r,o=!1,s=a(async()=>{if(r)return o=!0,r;r=(async()=>{do{o=!1;try{await e()}catch{}}while(o)})();try{await r}finally{r=void 0}},"exec");return{trigger(){n&&clearTimeout(n),n=setTimeout(()=>{n=void 0,s()},t),n.unref?.()},async flush(){n&&(clearTimeout(n),n=void 0),await s()},cancel(){n&&clearTimeout(n),n=void 0}}}async function Qe(e){if(e)try{return await Oe(e).getFlows()}catch{return}}var fd,hd,md,vo,et=q(()=>{"use strict";Je();yr();Pe();fd=7,hd=864e5,md=a(()=>fd,"ttlDaysProvider"),vo=a(()=>Date.now(),"nowProvider");a(gd,"configureProposalTtl");a(yd,"proposalTtlMs");a(Tf,"ttlExpiredReason");a(ko,"isPastTtl");a(ne,"expireByTtl");a(oi,"rebaseProposal");a(si,"rebaseInfoFrom");a(Ze,"syncOneProposal");a(wr,"syncOpenProposals");a($e,"rebaseOpenProposalsWith");a(wd,"createDebouncedSync");a(Qe,"tryRawLive")});var Rd={};Ki(Rd,{applyFlowNodeOps:()=>Gt,applyReplaceTextToValue:()=>Td,buildPatchedFlowTab:()=>br,collectSetProps:()=>ii,decodeBase64UrlJson:()=>vr,findTextOccurrences:()=>xd,interpretPatch:()=>kr});function Sd(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function Rf(e){if(typeof e!="object"||e===null||Array.isArray(e))throw new b(`add op needs node as an object (got ${Sd(e)})`,"invalid_node")}async function br(e,t,n){return Gt(e,t,n.map(r=>({op:"set",nodeId:r.nodeId,set:r.set})))}function xd(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 Td(e,t,n,r,o){if(Ad.has(t))throw new b(`Cannot change protected node property '${t}' via replace_text`,"protected_property");if(typeof n!="string"||n.length===0)throw new b("find must be a non-empty string","missing_find");if(typeof r!="string")throw new b("replace must be a string","invalid_replace");if(e===void 0)throw new b(`Field '${t}' was not found on the node`,"field_not_found");if(typeof e!="string")throw new b(`Field '${t}' is not a string (got ${e===null?"null":typeof e})`,"field_not_string");let s=xd(e,n);if(s.length===0)throw new b(`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 b(`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 b(`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 b(`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 Pf(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 ${Sd(r)})`});continue}let o=r;(!o.id||typeof o.id!="string")&&t.push({code:"missing_node_id",error:"add op needs node.id"});try{yt(o.type,"node.type")}catch(s){s instanceof b&&t.push({code:s.code,error:s.message,nodeId:o.id})}"wires"in o&&wn(o.wires,t,typeof o.id=="string"?o.id:void 0)}else n.op==="set"&&typeof n.set=="object"&&"wires"in n.set&&wn(n.set.wires,t,n.nodeId);return t}function _f(e){let t=new Set;for(let n of e)n.op==="add"&&typeof n.node.id=="string"?t.add(n.node.id):n.op!=="add"&&typeof n.nodeId=="string"&&t.add(n.nodeId);return t}async function Gt(e,t,n,r={}){if(!t)throw new b("flow id is required","missing_flow_id");if(!n.length)throw new b("At least one node edit is required","missing_edits");let o=Pf(n);o.push(...Os([n.flatMap(m=>m.op==="add"?[m.node]:[])],"the batch (add ops)")),at(o);let s=await e.getFlow(t);if(r.expectedRev!==void 0&&s.rev!==r.expectedRev){let m=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:m}}}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(m=>({...m})),u=[];for(let m of n)try{m.op==="add"&&r.nodeTypes&&await Ca(r.nodeTypes,m.node.type),vd(c,t,m)}catch(g){if(g instanceof b)u.push({code:g.code,error:g.message,...g.statusCode!==400?{statusCode:g.statusCode}:{},...g.details??{}});else throw g}u.push(...Ms(c,t));let p=_f(n),f=await $s(c,r.nodeTypes,{softWarningNodeIds:p});u.push(...f.errors),at(u);for(let m of n)vd(i,t,m);let h=[...f.warnings];if(r.nodeTypes){let m=await Ds(i,d,r.nodeTypes);h.push(...m.filter(g=>!g.nodeId||p.has(g.nodeId)))}return{expectedRev:s.rev,flow:l,...h.length>0?{warnings:h}:{}}}function bd(e,t,n){if(!t||typeof t!="string")throw new b("Each edit needs a node id","missing_node_id");let r=e.findIndex(o=>o.id===t);if(r<0||!e[r])throw new b(`Node '${t}' was not found in flow '${n}'`,"node_not_found",{statusCode:404});return r}function vd(e,t,n){if(n.op==="add"){let i=n.node;if(!i.id||typeof i.id!="string")throw new b("add op needs node.id","missing_node_id");if(yt(i.type,"node.type"),e.some(d=>d.id===i.id))throw new b(`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=bd(e,n.nodeId,t);e.splice(i,1);return}let r=bd(e,n.nodeId,t),o=e[r];if(!o)throw new b(`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 b(`Field '${n.field}' was not found on the node`,"field_not_found");let i=Td(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(Ad.has(i))throw new b(`Cannot change protected node property '${i}' via GET propose`,"protected_property");if(i==="wires"){let l=[];wn(d,l,n.nodeId),at(l)}s[i]=d}e[r]=s}function kd(e){if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean"||typeof e=="bigint")return String(e)}function ii(e){let t={},n=e.set;if(n&&typeof n=="object"&&!Array.isArray(n))for(let[r,o]of Object.entries(n)){let s=kd(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=kd(o);i!==void 0&&(t[s]=i)}}return t}function vr(e){let t;try{t=Buffer.from(e,"base64url").toString("utf8")}catch{throw new b("patch must be base64url-encoded JSON","invalid_patch")}try{return JSON.parse(t)}catch{throw new b("patch JSON is malformed","invalid_patch_json")}}function Ef(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 ai(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 Cf(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 b("occurrence must be a positive integer","invalid_occurrence");if(!Number.isFinite(t))throw new b("occurrence must be a positive integer","invalid_occurrence");return Math.trunc(t)}function If(e){let t=e.node;if(t!==void 0)return Rf(t),{op:"add",node:t};let n={...e};return delete n.op,{op:"add",node:n}}function Lf(e){let t=ai(e);if(!t)throw new b("replace_text op needs node id","missing_node_id");let n=typeof e.field=="string"?e.field:"";if(!n)throw new b("replace_text op needs field","missing_field");if(typeof e.find!="string")throw new b("replace_text op needs find string","missing_find");if(typeof e.replace!="string")throw new b("replace_text op needs replace string","invalid_replace");let r=Cf(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 Nf(e){let t=ai(e);if(!t)throw new b("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 Ff(e){let t=typeof e.op=="string"?e.op.toLowerCase():"set";if(t==="add")return If(e);if(t==="remove"){let n=ai(e);if(!n)throw new b("remove op needs id","missing_node_id");return{op:"remove",nodeId:n}}if(t==="replace_text")return Lf(e);if(t!=="set")throw new b("nodes[].op must be 'add', 'remove', 'set', or 'replace_text'","invalid_patch");return Nf(e)}function kr(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw new b("patch must be a JSON object","invalid_patch");let n=e;if(n.type==="replace_flow"||n.flow&&typeof n.flow=="object"){let r=Ef(n,t);if(!r)throw new b("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 b("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 b("Each patch.nodes entry must be an object","invalid_patch");o.push(Ff(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 b("patch must include nodes[] edits or type/flow for replace_flow","invalid_patch")}var Ad,Ao=q(()=>{"use strict";ie();Hs();qt();Ad=new Set(["id","type","z"]);a(Sd,"describeNonObjectNode");a(Rf,"assertAddNodeObject");a(br,"buildPatchedFlowTab");a(xd,"findTextOccurrences");a(Td,"applyReplaceTextToValue");a(Pf,"collectOpShapeIssues");a(_f,"touchedNodeIdsFromOps");a(Gt,"applyFlowNodeOps");a(bd,"requireNodeIndex");a(vd,"applyOneNodeOp");a(kd,"stringifyQueryValue");a(ii,"collectSetProps");a(vr,"decodeBase64UrlJson");a(Ef,"resolveReplaceFlowId");a(ai,"resolveNodeId");a(Cf,"parseOccurrence");a(If,"parseAddOp");a(Lf,"parseReplaceTextOp");a(Nf,"parseSetOp");a(Ff,"parseNodeOp");a(kr,"interpretPatch")});function Kt(e,t){return Ut(e.createdBy)===t.id}function di(e,t){return!t||t.length===0||t.includes(e)}function Of(e){return typeof e=="string"&&_d.has(e)}function xn(e,t){let n=$f[e],r=e!=="status"&&Array.isArray(t);return new b(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 So(e,t){if(Array.isArray(t))throw xn(e,t);return t}function Mf(e){return typeof e=="number"?Number.isInteger(e)?e:null:typeof e=="string"&&/^-?\d+$/.test(e.trim())?Number(e.trim()):null}function li(e){return e==null||e===""}function Df(e){if(!li(e)){if(e==="true"||e==="1"||e===!0)return!0;if(e==="false"||e==="0"||e===!1)return!1;throw xn("mine",e)}}function ci(e){if(li(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(!Of(o))throw xn("status",e);n.includes(o)||n.push(o)}}return n.length>0?n:void 0}function Pd(e,t,n,r){if(li(t))return;let o=Mf(t);if(o===null||o<n||o>r)throw xn(e,t);return o}function xo(e){let t={},n=Df(So("mine",e.mine));n!==void 0&&(t.mine=n);let r=ci(e.status);r&&(t.status=r);let o=So("flowId",e.flowId??e.flow_id??e.flow);typeof o=="string"&&o.length>0&&(t.flowId=o);let s=Pd("limit",So("limit",e.limit),1,Sn);s!==void 0&&(t.limit=s);let i=Pd("offset",So("offset",e.offset),0,Number.MAX_SAFE_INTEGER);return i!==void 0&&(t.offset=i),t}function Jt(e){let t=e.offset+e.returned,n=t<e.total;return{total:e.total,offset:e.offset,...e.limit!==null?{limit:e.limit}:{},hasMore:n,...n?{nextOffset:t}:{}}}function Hf(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),Sn);return{items:e.slice(r,r+o),total:n,limit:o,offset:r}}function Tn(e,t,n={},r={}){let o=r.mine===!0?t.id:void 0,s={};o&&(s.linkId=o),r.flowId&&(s.flowId=r.flowId);let d=e.list(s).filter(c=>r.mine!==!1||!Kt(c,t)).filter(c=>di(c.status,r.status)).map(c=>({...pe(lt(c),n),mine:Kt(c,t)})),l=Hf(d,r);return{proposals:l.items,total:l.total,limit:l.limit,offset:l.offset}}function ct(e,t,n,r={}){if(!n)throw new b("Missing proposal id","missing_proposal_id");let o=e.get(n);return{...pe(lt(o),r),mine:Kt(o,t)}}function jf(e){let t={kind:"access-link",linkId:e.id};return typeof e.label=="string"&&e.label&&(t.label=e.label),t}function Rn(e,t,n,r,o,s={}){if(!r)throw new b("Missing proposal id","missing_proposal_id");let i;try{i=e.get(r)}catch(f){throw f instanceof me,f}if(!Kt(i,n))throw new jt(r);if(!U(i.status))throw new Y(r,i.status);let d=typeof o=="string"&&o.trim()?o.replace(/\0/g,"").trim().slice(0,2e3):"Withdrawn by access link",l=jf(n),c=e.markWithdrawn(r,l,d),u=pe(lt(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 Uf(e,t,n,r={}){let o;try{o=e.get(t.id)}catch{return{}}if(r.live){$e(e,r.live,{flowId:o.payload.flowId});try{o=e.get(t.id)}catch{return{}}}return U(o.status)?Ye(Ve(e,o,n)):{}}function he(e,t,n,r={},o={}){let s="mine"in n?n:{...pe(n,r),mine:Kt(n,t)},i=0;if(o.live)try{let c=e.get(s.id).payload.flowId;i=$e(e,o.live,{flowId:c}).expired;let u=e.get(s.id);s={...pe(u,r),mine:Kt(u,t)}}catch{}let d=fe(lt(s),r),l={...d,mine:s.mine,...ue(i)};return o.includeConflicts!==!1&&U(d.status)&&Object.assign(l,Uf(e,d,t.id)),l}async function ut(e){let t=await e.getFlows();return{rev:t.rev,flows:t.flows}}async function Me(e,t){if(!t)throw new b("Missing flow id","missing_flow_id");let n=await e.getFlow(t);return{rev:n.rev,flow:yn(t,n.flow)}}function Bf(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 Ed(e,t){if("nodes"in e&&e.nodes!==void 0&&!Array.isArray(e.nodes))throw new b("payload.nodes must be an array of node ops","invalid_nodes");if(t!==void 0&&!Array.isArray(t))throw new b("nodes must be an array of node ops","invalid_nodes")}async function qf(e,t,n,r,o){let{applyFlowNodeOps:s,interpretPatch:i}=await Promise.resolve().then(()=>(Ao(),Rd));Ed(n,r);let d=Array.isArray(n.nodes)?n.nodes:Array.isArray(r)?r:null;if(!d)throw new b("payload.flow is required","missing_flow");let l=n.flowId,c=typeof l=="string"?l:"";if(!c)throw new b("payload.flowId is required","missing_flow_id");let u=i({nodes:d,flowId:c},c);if(u.kind!=="nodes")throw new b("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 te(t,p.expectedRev,"submit_proposal");return{flowId:u.flowId,flow:p.flow}}async function zf(e,t,n,r,o){let s=n.flowId,i=n.flow,d=!i||typeof i!="object"||Array.isArray(i);if(d&&Ed(n,r),d&&(Array.isArray(n.nodes)||Array.isArray(r))){let l=await qf(e,t,n,r,o);s=l.flowId,i=l.flow}if(typeof s!="string"||s.length===0)throw new b("payload.flowId is required","missing_flow_id");if(!i||typeof i!="object"||Array.isArray(i))throw new b("payload.flow is required","missing_flow");return{flowId:s,flow:i}}function Wf(e,t){try{return{safeExpectedRev:We(e.slice(0,200),"expectedRev"),safeFlowId:We(t.slice(0,200),"flowId")}}catch{throw new b("expectedRev and payload.flowId must be safe path segments","invalid_path_segment")}}function Gf(e,t){let n=nd(it(e),t);return n.unresolved.length>0&&at(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 Cd(e,t,n){let r=e.type,o=Bf(e),s=e.payload;if(r!=="replace_flow")throw new b("type must be 'replace_flow'","invalid_proposal_type");if(!o)throw new b("expectedRev is required (top-level field next to type and payload)","missing_expected_rev");if(!s||typeof s!="object"||Array.isArray(s))throw new b("payload must be an object","invalid_payload");let i=s,d=Oe(t),l=await d.getFlows(),c=so(l,d.kind);if(l.rev!==o){let T=Ts(d)?.get(o);if(!T)throw new te(o,l.rev,"submit_proposal");c=so(T,d.kind)}let u=await zf(kt(c),o,i,e.nodes,n),{safeExpectedRev:p,safeFlowId:f}=Wf(o,u.flowId),h=u.flow,m=Buffer.byteLength(JSON.stringify(s),"utf8");if(m>K)throw new le("proposal payload",m,K);at(Na(h,f));let y=await c.getFlows();if(y.rev!==p)throw new te(p,y.rev,"submit_proposal");let A=Gf(h,y.flows),x=it(A.flow);at(La(x,f,y.flows));let $=await c.getFlow(f);if($.rev!==p)throw new te(p,$.rev,"submit_proposal");let L=it($.flow);n&&await Ea(n,x);let{errors:D,warnings:H}=await Fa(x,{flowId:f,baseFlow:L,...n?{nodeTypes:n}:{}});return at(D),H.unshift(...fr(L,x).map(T=>({...T})),...od(A.restoredFields,x,y.flows).map(T=>({...T}))),{safeExpectedRev:p,safeFlowId:f,safeFlow:x,safeBaseFlow:L,restoration:A,flowWarnings:H,head:l}}function Id(e){let t={kind:"access-link",linkId:e.id};return e.label!==void 0&&(t.label=e.label),t}async function Tt(e,t,n,r,o,s={},i){let d=await Cd(r,o,i),{safeExpectedRev:l,safeFlowId:c,safeFlow:u,safeBaseFlow:p,restoration:f,flowWarnings:h}=d,m=Id(n),g=n.oauthClientId&&n.oauthUser?{kind:"oauth",clientId:n.oauthClientId,user:n.oauthUser}:m,y=ar(r.title)??Bs(p,u),A=e.create({type:"replace_flow",expectedRev:l,payload:{flowId:c,flow:u},baseFlow:p,...f.restoredFields.length>0?{restoredSecrets:f.restoredFields}:{},title:y,createdBy:g,...h.length>0?{warnings:h}:{}}),x=Ze(e,A,d.head).proposal,$=pe(lt(x),s),L=$.flowLabel||void 0;return t.append("proposal.created",m,{proposalId:x.id,flowId:c,expectedRev:l,...d.head.rev!==l?{liveRev:d.head.rev}:{},...x.rebase?{rebaseState:x.rebase.state}:{},title:$.title,displayName:$.displayName,...L?{flowLabel:L}:{},...h.length>0?{warningCount:h.length}:{},...f.restored>0?{restoredRedactions:f.restored}:{}}),$}async function To(e,t,n,r,o,s,i={},d){if(!r)throw new b("Missing proposal id","missing_proposal_id");let l=e.get(r);if(!Kt(l,n))throw new jt(r);if(!U(l.status))throw new Y(r,l.status);let c=await Cd({type:"replace_flow",...o,payload:o.payload??{flowId:l.payload.flowId}},s,d);if(c.safeFlowId!==l.payload.flowId)throw new b(`payload.flowId must stay '${l.payload.flowId}' (submit a new proposal for another flow)`,"flow_id_mismatch");let u=ar(o.title),p=e.updateBody(r,{baseFlow:c.safeBaseFlow,flow:c.safeFlow,expectedRev:c.safeExpectedRev,restoredSecrets:c.restoration.restoredFields,warnings:c.flowWarnings,...u?{title:u}:{}}),f=Ze(e,p,c.head).proposal,h=pe(lt(f),i);return t.append("proposal.resolved",Id(n),{proposalId:r,flowId:f.payload.flowId,expectedRev:c.safeExpectedRev,...c.restoration.restored>0?{restoredRedactions:c.restoration.restored}:{},title:h.title,displayName:h.displayName,...f.rebase?{rebaseState:f.rebase.state}:{}}),{...h,mine:!0}}async function Ld(e,t,n,r,o,s,i){if(s!=="live"&&s!=="proposed")throw new b("choice must be 'live' or 'proposed'","invalid_choice");if(typeof o!="string"||o.length===0||o.length>200)throw new b("nodeId is required","missing_node_id");let d=await Oe(i).getFlows(),l=e.get(r);if(!U(l.status))throw new Y(r,l.status);let c=Ze(e,l,d).proposal,u=c.rebase?.conflicts.find(m=>m.nodeId===o);if(!u)throw new b("No current conflict for that node (it may already be resolved). Reload the proposal.","conflict_not_found",{statusCode:409});if(!u.choices.includes(s))throw new b("That choice is not available for this conflict.","invalid_choice");let p;try{p=pd({flowId:c.payload.flowId,base:c.baseFlow,proposed:c.payload.flow,liveFlows:d.flows,nodeId:o,choice:s})}catch(m){throw m instanceof pr?new Ke(r,c.rebase?.conflicts??[]):new b("This conflict cannot be resolved by hand.","invalid_choice")}let f=e.updateBody(r,{baseFlow:it(p.base),flow:it(p.proposed),warnings:[...(c.warnings??[]).filter(m=>m.code!=="secret_may_revert"||m.nodeId!==o),...p.warnings]}),h=Ze(e,f,d).proposal;return t.append("proposal.conflict_resolved",n,{proposalId:r,flowId:h.payload.flowId,nodeId:o,choice:s,kind:u.kind,status:h.status}),h}async function Ar(e,t){let n=Oe(e),r;try{r=(await n.getFlows()).rev}catch{return e}if(r===t)return e;let o=Ts(n)?.get(t);if(!o)return e;let s=so(o,n.kind);return e===n?s:kt(s)}function W(e,t="rest"){return Sr(e,t)}var Sn,_d,$f,b,ie=q(()=>{"use strict";Te();ro();Rs();Je();Zr();se();tt();Hs();qt();zt();Fe();yr();et();Pe();a(Kt,"isMineProposal");Sn=200,_d=new Set(["pending","conflict","conflict","approved","rejected","failed","withdrawn","expired","superseded"]);a(di,"matchesStatusFilter");a(Of,"isProposalStatus");$f={status:[..._d].join(", "),limit:`integer 1\u2013${Sn}`,offset:"integer \u2265 0",mine:"true, false, 1, 0",flowId:"one flow tab id"};a(xn,"invalidFilterError");a(So,"singleQueryValue");a(Mf,"parseIntegerFilter");a(li,"isUnset");a(Df,"parseMineFilter");a(ci,"parseStatusFilter");a(Pd,"parseRangeFilter");a(xo,"parseListProposalsQuery");a(Jt,"listPageFields");a(Hf,"applyListPagination");a(Tn,"listAccessibleProposals");a(ct,"getAccessibleProposal");a(jf,"accessLinkActor");a(Rn,"withdrawMyProposal");a(Uf,"conflictsForProposal");a(he,"presentAccessibleProposalForApi");b=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(ut,"listFlows");a(Me,"getFlow");a(Bf,"resolveExpectedRev");a(Ed,"assertNodesArrayOrAbsent");a(qf,"materializePayloadFromNodes");a(zf,"resolveReplaceFlowPayload");a(Wf,"assertSafeRevAndFlowId");a(Gf,"strictRestoreOrThrow");a(Cd,"prepareReplaceFlowSubmission");a(Id,"auditActorFor");a(Tt,"submitReplaceFlowProposal");a(To,"resolveMyProposal");a(Ld,"resolveConflictChoice");a(Ar,"adapterAtRev");a(W,"aiOpsErrorToHttp")});function ui(e){return e==="get_link"?86400:2592e3}function pi(e,t){return e==="get_link"?`get_link ttlSeconds must be \u2264 ${t} (24 hours)`:`ttlSeconds must be \u2264 ${t} (30 days) for ${e} tokens`}var Vt,xr=q(()=>{"use strict";a(ui,"maxTtlSecondsForKind");a(pi,"ttlCapErrorMessage");Vt="ttlSeconds must be a positive number of seconds within the cap for the token kind: get_link \u2264 86400 (24h); rest and mcp \u2264 2592000 (30 days)."});function R(e,t,n,r,o=re,s="error"){return{code:e,status:t,meaning:n,hint:r,channels:o,kind:s}}function Tr(e){return Jf.get(e)?.hint}function Nd(e){return e.replace(/\\/g,"\\\\").replace(/\|/g,"\\|")}function Fd(){let e=a(s=>`| \`${s.code}\` | ${s.status} | ${Nd(s.meaning)} | ${Nd(s.hint)} | ${s.channels.join(", ")} |`,"row"),t=["| Code | HTTP | Meaning | Next step | Channels |","| --- | --- | --- | --- | --- |"],n=Po.filter(s=>s.kind==="error"),r=Po.filter(s=>s.kind==="warning"),o=Po.filter(s=>s.kind==="status");return[...t,...n.map(e),"","**Warnings** (in `warnings[]` on a successful propose; never blocking):","",...t,...r.map(e),"","**Proposal statuses.** Open proposals are closed automatically: `expired` after the configured TTL (default 7 days). Revision changes auto-rebase open proposals; `conflict` blocks approval until resolved. Legacy closed proposals stay closed. Submit, get, and list responses include `autoClosedCount` when that request auto-closed proposals (omitted when zero).","",...t,...o.map(e)].join(`
|
|
10
|
+
`)}var re,Pn,Ro,Kf,Rt,Po,Jf,_o=q(()=>{"use strict";xr();re=["GET","REST","MCP"],Pn=["GET"],Ro=["REST","MCP"],Kf=["GET","REST"],Rt=["admin"];a(R,"e");Po=[R("duplicate_env_keys",200,"Changed env arrays contain duplicate names; pairing by name is ambiguous when entries are reordered","Review the env values before approval",[...re,"admin"],"warning"),R("approve_queue_timeout",504,"Approval exceeded its queue-wait deadline (default 60 seconds); no deployment was started for this request","Check the live flow and retry approval",Rt),R("deploy_timeout",504,"Deployment exceeded its deadline (default 60 seconds); approvals wait for settlement or the hard cap (default 5 minutes)","Before the cap, late settlement updates the proposal; after the cap, deployPending.queueReleased is true and the outcome stays unknown. Check the live flow before approving again; approval always re-reads live",Rt),R("secret_may_revert",200,"Secret spans could not be aligned during proposed conflict resolution; proposed values were kept","Review the warned field against live flows before approval; an editor secret change may be reverted",[...re,"admin"],"warning"),R("proposal_conflict",409,"Editor and proposal changes conflict (conflicts contains field names only)","Read the current flow and resolve the proposal before approval",[...re,"admin"]),R("rebase_ack_required",409,"The merged result needs review (mergedHash included)","Review the diff and approve with its mergedHash",["admin"]),R("rebase_race",409,"Flows kept changing during approval","Recheck the proposal and retry approval",["admin"]),R("conflict_not_found",409,"The requested conflict no longer exists","Reload the proposal and its conflicts",["admin"]),R("invalid_choice",400,"Invalid manual conflict choice","Use an available live or proposed choice",["admin"]),R("flow_id_mismatch",400,"Resolution tried to change the proposal flow id","Keep the same flowId or submit a new proposal",Ro),R("access_link_missing",401,"No Bearer / path token","Ask the user for a new access link of the right kind"),R("bearer_scheme_required",401,"Auth used Basic (or non-Bearer)","Send `Authorization: Bearer <token>`",Ro),R("token_expired",401,"Known token past `expiresAt` (body includes `expiredAt` and `accessLinksUrl`)","Ask the user to create a new link on the `/ai-access` dashboard \u2192 Access tokens (`accessLinksUrl`)"),R("access_link_invalid",401,"Unknown, revoked, or cleared after restart","Ask the user for a new access link"),R("wrong_token_kind",403,"Token kind does not match this surface","Mint the matching kind on `/ai-access` \u2192 Access tokens; show `tellUser` to the human"),R("ai_features_disabled",403,"Plugin AI features off","Owner enables AI Access / `adminAuth`"),R("auth_required",401,"Admin session required","Sign in to the Node-RED editor",Rt),R("insufficient_permissions",403,"Admin lacks required scope","Need `*` for link mint / approve",Rt),R("invalid_link_kind",400,"Unknown `kind` on create","Use `get_link`, `rest`, or `mcp`",Rt),R("invalid_ttl",400,"`ttlSeconds` is zero, negative, not a number, or over the cap for the kind",Vt,Rt),R("access_link_limit",409,"Too many active links","Revoke an existing link first",Rt),R("access_link_not_found",404,"Unknown link id on revoke","List links and use an existing id",Rt),R("link_create_not_supported",403,"Attempted mint under a GET link","Admin mints via `POST /mcp/api/v1/links` only",Pn),R("rate_limited",429,"Per-IP or per-token limit","Wait `retryAfter` / `Retry-After`"),R("payload_too_large",413,"Body / proposal over ~1.5MB","Shrink content"),R("invalid_json",400,"Malformed JSON body","Fix Content-Type / body",Ro),R("unsupported_media_type",415,"POST body is not `application/json`","Send `Content-Type: application/json`",Ro),R("url_too_long",414,"GET URL over soft cap (~8KB)","Use chunked drafts or REST/MCP",Pn),R("not_found",404,"Unknown path or resource","Re-read docs / discovery"),R("missing_query",400,"Required search `q` missing","Pass `?q=`",Kf),R("invalid_filter",400,"Invalid list filter (`status`, `limit`, `offset`, `mine`); body names `parameter` and `allowed`","Use an allowed value: status one of the proposal statuses; limit integer 1\u2013200; offset integer \u2265 0; mine true|false"),R("invalid_arguments",400,"MCP tool arguments failed schema validation; body names `parameter`","Fix the named argument to match the tool input schema (see tools/list)",["MCP"]),R("invalid_diff_format",400,"Unknown `format` on proposal diff","Use `format=summary` (default) or `format=full`"),R("confirm_required",400,"GET withdraw without `confirm=1`","Repeat the request with `confirm=1`",Pn),R("missing_expected_rev",400,"Proposal submitted without `expectedRev` / `rev`","Read the flow and pass its `rev`"),R("version_mismatch",409,"Unknown or unavailable `rev` / `expectedRev` snapshot","Re-read flows and resubmit"),R("proposal_not_found",404,"Unknown proposal id","List proposals and use an existing id"),R("proposal_not_pending",409,"Action needs an open proposal","Only pending or conflict proposals can withdraw / decide"),R("proposal_not_owned",403,"Withdraw of another token\u2019s proposal","Only the creating token can withdraw"),R("validation_failed",400,"Several validation errors at once (message `N validation errors`)","Fix every entry in errors[] and resubmit."),R("duplicate_node_id",400,"The same node id appears more than once in a full tab or batch","Give every node a unique id"),R("missing_node_id",400,"Node / op without an id","Include a non-empty string id"),R("missing_node_type",400,"Node without a `type`","Set `type` to an installed palette type name"),R("invalid_node_type",400,"`type` is not a string","Use the palette type name exactly"),R("invalid_nodes",400,"`flow.nodes` / `flow.configs` not an array","Send arrays (use `[]` to clear)"),R("invalid_node",400,"Node entry is not an object","Send `{ id, type, \u2026 }` objects"),R("invalid_wires",400,"Bad wires shape, missing target, or more ports than outputs","Wire only to existing node ids / ports"),R("z_mismatch",400,"Node `z` differs from the flow id","Set `z` to the tab id or omit it"),R("node_already_exists",400,"`add` op for an id already in the flow","Use `set` or a new id"),R("protected_property",400,"Tried to change id / type / z","Omit id, type, and z from edits"),R("node_not_found",404,"Unknown node (single error only; several errors \u2192 `validation_failed`)","Re-read the flow"),R("field_not_found",404,"Unknown field on a node","Re-read the node"),R("text_not_found",400,"`replace_text` find had no match","Re-read field; find is exact"),R("ambiguous_match",409,"`replace_text` matched more than once","Pass `occurrence` (1-based) or unique find"),R("unknown_node_type",400,"Proposal uses a type that is not installed","List node-types / palette search; ask user to install"),R("node_type_not_found",404,"Unknown type on node-types detail","List types or search palette"),R("chunk_conflict",409,"Same draft seq re-sent with different data","Re-send identical data or start a new draft",Pn),R("missing_chunks",400,"Draft submit with gaps in seq","Upload every seq from 0 through max",Pn),R("draft_hash_mismatch",400,"Draft sha256 does not match assembled bytes","Send the hex sha256 of the assembled UTF-8",Pn),R("missing_config_node",201,"Warning: node has no (or an unresolved) config node yet","Configure before deploy if needed; see `candidates`",re,"warning"),R("unwired_node",201,"Warning: node has outputs but no wires","Wire it, or ignore while scaffolding",re,"warning"),R("incomplete_wires",201,"Warning: fewer wire ports than outputs","Add ports if outputs should connect",re,"warning"),R("redacted_placeholder_mismatch",400,"A `[REDACTED]` placeholder was added, duplicated, moved, or its env/header name changed","Keep placeholders exactly where they were shown (same line, same name) or supply a real value",re),R("secret_destination_changed",201,"Warning: a node holding a restored secret changed where it sends data (url/host/method/headers/\u2026)","Reviewer must verify the new destination before approving",re,"warning"),R("secret_node_code_changed",201,"Warning: a node holding a restored secret had its code changed","Reviewer must check the code does not send the secret elsewhere",re,"warning"),R("secret_wires_changed",201,"Warning: a node holding a restored secret had its output wires changed","Reviewer must check where its messages now go",re,"warning"),R("secret_scope_changed",201,"Warning: tab/group env or a config node scoping a secret-holding node changed","Reviewer must check env-resolved hosts/URLs (e.g. `${API_HOST}`)",re,"warning"),R("outbound_node_changed",201,"Warning: proposal adds/changes an outbound-capable node (http request/response, mqtt, tcp, udp, email, websocket, link out/call, function, exec, file, or any node with url/host fields)","Reviewer must check it cannot send secret data elsewhere",re,"warning"),R("secret_tab_changed",201,"Warning: proposal with restored secrets also changes other nodes in the tab","Review every change before approving",re,"warning"),R("secret_reference_added",201,"Warning: an added/changed node now reads a secret-named env/global/flow value (env.get, $env, ${\u2026}, typed input)","Reviewer must check the value is not sent anywhere unexpected",re,"warning"),R("conflict",200,"Status: open proposal has conflicting editor changes; approval blocked","Read the current flow and resolve the proposal",re,"status"),R("expired",200,"Status: open proposal expired after the configured TTL (default 7 days)","Re-read the flow and submit a new proposal",re,"status"),R("superseded",200,"Legacy closed status; new approvals rebase other open proposals","Re-read the flow and submit a new proposal if still needed",re,"status"),R("upstream_error",502,"Node-RED runtime/flows error","Retry; check Node-RED logs"),R("internal_error",500,"Unexpected server error","Retry; check Node-RED logs")],Jf=new Map(Po.map(e=>[e.code,e]));a(Tr,"registryHint");a(Nd,"cell");a(Fd,"renderGetLinkErrorTable")});function Od(e){let t=pe(e),n=po(e)||void 0;return{proposalId:e.id,title:t.title,displayName:t.displayName,flowId:e.payload.flowId,...n?{flowLabel:n}:{}}}function fi(e,t){return e!==void 0&&Number.isFinite(e)&&e>0?e:t}var Yt,Xt,Pt,Zt,Eo,Vf,$d,Co,Io=q(()=>{"use strict";Te();Je();Fe();yr();et();Pe();a(Od,"auditProposalMeta");Yt=class extends Error{static{a(this,"ProposalRebaseAckRequiredError")}code="rebase_ack_required";statusCode=409;mergedHash;rebaseState;constructor(t,n,r){super(`Proposal '${t}' was rebased onto newer editor changes since you viewed it. Review the updated diff, then approve again with the new mergedHash.`),this.name="ProposalRebaseAckRequiredError",this.mergedHash=n,this.rebaseState=r}},Xt=class extends Error{static{a(this,"ProposalRebaseRaceError")}code="rebase_race";statusCode=409;constructor(t){super(`Proposal '${t}' could not be applied because the flows kept changing during approve. It is still pending \u2014 try again.`),this.name="ProposalRebaseRaceError"}},Pt=class extends Error{static{a(this,"ProposalDeployTimeoutError")}code="deploy_timeout";statusCode=504;constructor(){super("Deployment timed out; the deploy outcome is unknown. The proposal remains pending with deployPending set. Approvals wait until the runtime settles or the deploy hard cap is reached. Check the live flow before approving again."),this.name="ProposalDeployTimeoutError"}},Zt=class extends Error{static{a(this,"ProposalApproveQueueTimeoutError")}code="approve_queue_timeout";statusCode=504;constructor(){super("Approval timed out waiting in the queue. No deployment was started for this request. Check the live flow and retry."),this.name="ProposalApproveQueueTimeoutError"}},Eo=class extends Error{static{a(this,"DeployHardCapError")}},Vf=3;a(fi,"deadlineMs");$d=Promise.resolve(),Co=class{constructor(t,n,r,o,s={}){this.proposals=t;this.audit=n;this.adapter=r;this.getDeploymentType=o;this.serviceOptions=s}static{a(this,"ProposalService")}async approve(t,n,r={}){let o=this.proposals.get(t),s=o.deployPending!==void 0;if(!U(o.status))throw new Y(t,o.status);if(!s&&this.proposals.isApproving(t))throw new Re(t);let i=$d,d,l=new Promise(c=>{d=c});return $d=i.then(()=>l),new Promise((c,u)=>{let p=!1,f,h=new Promise((g,y)=>{f=setTimeout(()=>y(new Zt),fi(this.serviceOptions.approveQueueTimeoutMs,6e4)),f.unref?.()});a(async()=>{try{await Promise.race([i,h]),clearTimeout(f);let g=this.proposals.get(t);if(s&&g.status==="approved")return g;let y=this.proposals.beginApprove(t);return p=!0,await this.approveLocked(y,n,r,u)}finally{clearTimeout(f),p&&this.proposals.releaseLock(t),d()}},"run")().then(c,u)})}async approveLocked(t,n,r,o){let s=t.id,i,d=Od(t),l=r.deploymentType??this.getDeploymentType(),c=t.type;if(c!=="replace_flow"){let f=this.proposals.markFailed(s,n,`Unsupported proposal type '${c}'`);return this.audit.append("proposal.failed",n,{...d,reason:f.reason}),f}if(ko(t))throw this.proposals.releaseLock(s),ne(this.proposals,{flowId:t.payload.flowId}),new Y(s,this.proposals.get(s).status);let u=Oe(this.adapter),p=a((f,h)=>{let m=this.proposals.markApproved(s,n,h);return this.audit.append("proposal.approved",n,{...d,resultRev:h,liveRev:h,baseRev:f.expectedRev,alreadyApplied:!0}),m},"markAlreadyApplied");for(let f=0;f<Vf;f++){let h=await u.getFlows(),m=this.proposals.get(s),g=Wt(h.flows,m.payload.flowId,m.payload.flow),y=g&&gr(g);if(y&&(y===m.deployPending?.mergedHash||y===gr(m.payload.flow))){i=p(m,h.rev);break}let{result:A}=Ze(this.proposals,m,h,{duringApprove:!0});if(A.state==="conflict"||!A.merged||!A.mergedHash)throw this.audit.append("proposal.conflict",n,{...d,liveRev:h.rev,conflictCount:A.conflicts.length}),new Ke(s,si(A).conflicts);if(y===A.mergedHash){i=p(m,h.rev);break}if((A.state==="merged"||r.mergedHash!==void 0)&&r.mergedHash!==A.mergedHash)throw new Yt(s,A.mergedHash,A.state);let $=A.merged,L=A.mergedHash,D=!1;try{let H=await this.withDeployDeadline(()=>u.updateFlow({flowId:m.payload.flowId,flow:$,expectedRev:h.rev,deploymentType:l}),()=>{D=!0;let T=new Pt;this.proposals.recordDeployPending(s,{mergedHash:L,reason:"Deploy outcome unknown: the runtime has not settled. Approvals are waiting."}),this.audit.append("proposal.deploy_timeout",n,{...d,code:T.code}),o(T)},()=>{this.proposals.recordDeployPending(s,{mergedHash:L,queueReleased:!0,reason:"Deploy outcome unknown: the runtime did not settle before the hard cap. The approval queue was released. Check the live flow before approving again."}),o(new Pt)});i=this.proposals.markApproved(s,n,H.rev),this.audit.append("proposal.approved",n,{...d,resultRev:H.rev,flowId:m.payload.flowId,baseRev:m.expectedRev,liveRev:h.rev,rebaseState:A.state});break}catch(H){if(i||H instanceof Eo)throw H;if(D)throw this.proposals.recordDeployPending(s,void 0),H;if(H instanceof te)continue;let T=H instanceof Error?H.message:String(H),k=this.proposals.markFailed(s,n,T);return this.audit.append("proposal.failed",n,{...d,reason:T}),k}}if(!i)throw new Xt(s);try{await wr(this.proposals,this.adapter,{flowId:i.payload.flowId})}catch{}return i}async withDeployDeadline(t,n,r){let o=setTimeout(n,fi(this.serviceOptions.deployTimeoutMs,6e4));o.unref?.();let s,i=new Promise((d,l)=>{s=setTimeout(()=>{try{r()}finally{l(new Eo)}},fi(this.serviceOptions.deployHardCapMs,3e5)),s.unref?.()});try{return await Promise.race([t(),i])}finally{clearTimeout(o),clearTimeout(s)}}reject(t,n,r){let o=this.proposals.get(t);if(!U(o.status))throw new Y(t,o.status);let s=Od(o),i=this.proposals.markRejected(t,n,r.trim()||"Rejected by admin");return this.audit.append("proposal.rejected",n,{...s,reason:i.reason}),i}}});function we(e,t,n={}){return hi(e,t,n)}function Yf(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 Xf(e){return e==="mcp"?"Call list_node_types or search_palette. Ask the user to install via Manage palette, then recheck. This API never installs.":e==="rest"?"Call GET /node-types?q= or GET /palette/search?q=. Ask the user to install via Manage palette, then recheck. This API never installs.":"Call GET \u2026/node-types?q= or \u2026/palette/search?q=. Ask the user to install via Manage palette, then recheck. This API never installs."}function _t(e,t,n,r){return e==="mcp"?t:e==="rest"?n:r}function hi(e,t,n={}){switch(e){case"version_mismatch":return _t(t,"Re-read with list_flows/get_flow and retry with the current rev.","Re-read the flow and retry with the current rev.","Re-read the flow and retry with the current rev.");case"node_not_found":return _t(t,"Call get_flow or get_node and use a node id from that response.","Call GET /flows/{id} and use a node id from that response.","Call GET \u2026/flows/<id> and use a node id from that response.");case"flow_not_found":return _t(t,"Call list_flows to list valid ids.","Call GET /flows to list valid ids.","Use an id from GET ./flows.");case"proposal_not_found":return _t(t,"Call list_proposals and use an id from that response.","List proposals and use an id from that response.","List proposals and use an id from that response.");case"proposal_not_owned":return"Only the access link that created an open proposal can withdraw it.";case"proposal_not_pending":return"Only open (pending or conflict) proposals can be withdrawn or rejected.";case"confirm_required":return"Add confirm=1 to the withdraw URL (required so link prefetchers cannot withdraw).";case"invalid_wires":return Yf(t,n.cause);case"node_type_not_found":case"unknown_node_type":return Xf(t);case"missing_node_type":return'Each add op needs node.type as a non-empty string (e.g. "inject").';case"invalid_line_range":return _t(t,'Pass lines as "A" or "A-B" (1-based inclusive). Reversed ranges (end < start) are invalid.',"Use lines=A or lines=A-B (1-based inclusive). Reversed ranges (end < start) are invalid.","Use lines=A or lines=A-B (1-based inclusive). Reversed ranges (end < start) are invalid.");case"line_range_out_of_bounds":return"start is past the end of the field \u2014 check totalLines and request a lower lines range.";case"ambiguous_match":return"find matched more than once \u2014 pass occurrence=N (1-based) or a more unique find string.";case"missing_expected_rev":return _t(t,"Pass expectedRev from list_flows/get_flow (top-level, next to type and payload).","Pass expectedRev from GET /flows or GET /flows/{id} (top-level, next to type and payload).","Pass rev from GET \u2026/flows or \u2026/flows/<id>.");case"missing_query":return _t(t,"Pass a non-empty q string.","q query parameter is required.","q query parameter is required.");case"unsupported_media_type":return"Send Content-Type: application/json with a JSON body.";case"text_not_found":return"Re-read the node field; find must match exactly (case-sensitive).";case"field_not_found":return"Re-read the node; field must be an existing string property (template, func, \u2026).";case"field_not_string":return _t(t,"This tool only returns string fields (template, func, \u2026). Use get_node for other property types.","This endpoint only returns string fields (template, func, \u2026). Use GET /nodes/{id} for other property types.","This endpoint only returns string fields (template, func, \u2026). Use GET /nodes/{id} for other property types.");default:return}}function Sr(e,t,n={}){let r=n.canonicalizeMessage!==!1,o=null;if(e instanceof Ke?o={statusCode:409,body:{error:e.message,code:e.code,conflicts:e.conflicts}}:e instanceof Yt?o={statusCode:409,body:{error:e.message,code:e.code,mergedHash:e.mergedHash}}:e instanceof Xt||e instanceof Pt||e instanceof Zt||e instanceof Re?o={statusCode:e.statusCode,body:{error:e.message,code:e.code}}:e instanceof b?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,...e.details??{}}}:e instanceof te?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,expectedRev:e.expectedRev,currentRev:e.currentRev}}:e instanceof X?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,flowId:e.flowId}}:e instanceof me||e instanceof jt||e instanceof Y||e instanceof le?o={statusCode:e.statusCode,body:{error:e.message,code:e.code}}:(e instanceof ze||e instanceof Le)&&(o={statusCode:e.statusCode,body:{error:e.message,code:e.code}}),!o)return null;let s=o.body.code;if(r&&!Array.isArray(o.body.errors)){let i=nt[s];i&&(o.body.error=i)}if(typeof o.body.hint!="string"||!o.body.hint){let i=hi(s,t,{cause:o.body.cause})??Tr(s);i&&(o.body.hint=i)}return o}function Md(e,t,n={}){let o={error:n.error??nt[t]??"Request failed",code:t,...n.details??{}},s=hi(t,e,{cause:o.cause})??Tr(t);return s&&(o.hint=s),{statusCode:n.statusCode??400,body:o}}var nt,tt=q(()=>{"use strict";Te();Je();se();ie();_o();Io();nt={proposal_conflict:"Proposal conflicts with current editor changes",rebase_ack_required:"Review and acknowledge the merged proposal",rebase_race:"Flows kept changing during approval",deploy_timeout:"Deployment timed out; the deploy outcome is unknown. The proposal remains pending with deployPending set. Approvals wait until the runtime settles or the deploy hard cap is reached. Check the live flow before approving again.",approve_queue_timeout:"Approval timed out waiting in the queue. No deployment was started for this request. Check the live flow and retry.",conflict_not_found:"Conflict no longer exists",invalid_choice:"Invalid conflict choice",flow_id_mismatch:"Proposal flow id cannot change",node_not_found:"Node was not found",flow_not_found:"Flow was not found",field_not_found:"Field was not found",field_not_string:"Field is not a string",missing_query:"q query parameter is required",missing_flow_id:"Missing flow id",missing_node_id:"Missing node id",missing_field:"Missing field name",invalid_line_range:"lines must be A or A-B (1-based inclusive; end >= start)",line_range_out_of_bounds:"lines range is out of bounds",proposal_not_found:"Proposal was not found",proposal_not_owned:"Proposal was not created by this access link",proposal_not_pending:"Proposal is not pending",confirm_required:"Withdraw over GET requires confirm=1",unsupported_media_type:"Content-Type must be application/json",upstream_error:"Upstream request failed",internal_error:"Internal server error"};a(we,"channelErrorHint");a(Yf,"invalidWiresHint");a(Xf,"nodeTypeHint");a(_t,"pickChannel");a(hi,"hintFor");a(Sr,"mapErrorForChannel");a(Md,"buildChannelError")});var My={};Ki(My,{ACCESS_LINK_INVALID_MESSAGE:()=>Ot,ACCESS_LINK_MISSING_MESSAGE:()=>xe,AccessLinkStore:()=>an,EDITOR_SIDEBAR_ASSET_VERSION:()=>Cn,MAX_PROPOSAL_JSON_BYTES:()=>K,SettingsStore:()=>fn,acceptedMediaTypes:()=>Wc,areAiFeaturesEnabled:()=>Ce,bindSettingsStore:()=>Wr,createPluginServices:()=>us,default:()=>$y,getStorageDir:()=>ns,isAdminAuthEnabled:()=>oe,isMcpStreamRequest:()=>Gc,packageVersion:()=>En,registerRoutes:()=>Kc,renderEditorSidebarJs:()=>Lo,renderEditorSidebarLoaderHtml:()=>Ud,requireAccessLink:()=>Dt,resolveAiAccessSettings:()=>Ee});module.exports=eu(My);var zc=require("fs");var $r=require("fs"),Gn=require("path");se();function Mr(e){let t=(0,Gn.join)(e,".ai-access");return{root:t,proposals:(0,Gn.join)(t,"proposals"),logs:(0,Gn.join)(t,"logs"),settings:(0,Gn.join)(t,"config.json")}}a(Mr,"resolveAiAccessPaths");function ms(e){(0,$r.mkdirSync)(e,{recursive:!0,mode:hs});try{(0,$r.chmodSync)(e,hs)}catch{}}a(ms,"mkdirSecure");function Zi(e){ms(e.root),ms(e.proposals),ms(e.logs)}a(Zi,"ensureAiAccessDirs");$t();Be();function Vr(e){let t=Ae(e);return`auth-tokens${(t==="/"?"":t).replace(/\//g,"-")}`}a(Vr,"editorAuthTokenKey");Be();tt();function Zf(e){if(!e)return!1;let t=e.split(";")[0]?.trim().toLowerCase()??"";return t==="application/json"||t.endsWith("+json")}a(Zf,"isJsonContentType");function Qf(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(Qf,"hasRequestBody");function De(e="rest"){return(t,n,r)=>{let o=t.headers["content-type"];if(Zf(o)){r();return}if(!o&&!Qf(t)){r();return}let s=Md(e,"unsupported_media_type",{statusCode:415});n.status(s.statusCode).json(s.body)}}a(De,"requireJsonContentType");var eh="text/plain; charset=utf-8",th="text/html; charset=utf-8",nh="default-src 'none'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'";function rh(e){let t=e.query?.format;return typeof t=="string"?t.toLowerCase():void 0}a(rh,"formatParam");function oh(e){let t=rh(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(oh,"chooseGetLinkRepresentation");function Dd(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}a(Dd,"escapeHtml");var sh=/(?<![A-Za-z0-9_.~\-/])\.\/[A-Za-z0-9._~\-/]+(?:\?[A-Za-z0-9._~\-=&%]+)?/g;function ih(e){return e.replace(sh,(t,n)=>{if(e.startsWith("<",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(ih,"linkifyEscaped");function ah(e,t){return`<!doctype html>
|
|
11
|
+
<html><head><meta charset="utf-8">${t?`<base href="${Dd(t)}">`:""}<title>Node-RED AI Access</title></head><body><pre>${ih(Dd(e))}</pre></body></html>
|
|
12
|
+
`}a(ah,"renderGetLinkHtml");function dh(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(dh,"getLinkBaseHref");function lh(e){try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}}a(lh,"prettyJson");function Hd(){return(e,t,n)=>{let r=oh(e),o=dh(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",eh),s(u)):(t.setHeader("Content-Type",th),t.setHeader("Content-Security-Policy",nh),s(ah(l?lh(u):u,o)))}),n()}}a(Hd,"getLinkContentNegotiation");Dr();Be();function ch(e){let t=Se(e.settings.httpAdminRoot),n=ht(e.settings.httpAdminRoot),r="0.2.0-beta.7",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(ch,"buildDiscoveryIndex");function mi(e){return(t,n)=>{n.status(200).json(ch(e))}}a(mi,"discoveryIndexHandler");function jd(){return(e,t)=>{t.status(404).json({error:"Not found",code:"not_found"})}}a(jd,"mcpApiNotFoundHandler");var _n;function En(){return _n||(_n="0.2.0-beta.7",_n)}a(En,"packageVersion");var Cn=En();function uh(){return`#red-ui-sidebar-ai-access {
|
|
13
13
|
position: relative;
|
|
14
14
|
height: 100%;
|
|
15
15
|
display: flex;
|
|
@@ -165,7 +165,7 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
165
165
|
border: 0;
|
|
166
166
|
background: transparent;
|
|
167
167
|
color: transparent;
|
|
168
|
-
}`}a(
|
|
168
|
+
}`}a(uh,"editorSidebarStyles");function ph(){return`
|
|
169
169
|
/**
|
|
170
170
|
* Editor right-hand sidebar tab: AI Access.
|
|
171
171
|
* Served from /mcp/editor/ai-access-sidebar.js (no-store + ?v= cache-bust).
|
|
@@ -463,7 +463,7 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
463
463
|
saving = false;
|
|
464
464
|
});
|
|
465
465
|
}
|
|
466
|
-
`}a(
|
|
466
|
+
`}a(ph,"editorSidebarScriptPartA");function fh(){return`
|
|
467
467
|
function buildContent() {
|
|
468
468
|
var content = $('<div></div>', { id: CONTENT_ID, class: 'red-ui-sidebar-ai-access' });
|
|
469
469
|
|
|
@@ -565,7 +565,7 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
565
565
|
},
|
|
566
566
|
});
|
|
567
567
|
})();
|
|
568
|
-
`}a(
|
|
568
|
+
`}a(fh,"editorSidebarScriptPartB");function hh(){return ph()+fh()}a(hh,"editorSidebarScriptBody");function mh(){return`(function () {
|
|
569
569
|
if (typeof RED === 'undefined' || typeof BroadcastChannel === 'undefined') return;
|
|
570
570
|
if (window.__com55AiAccessRevealListener) return;
|
|
571
571
|
window.__com55AiAccessRevealListener = true;
|
|
@@ -596,8 +596,8 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
596
596
|
try { window.focus(); } catch (e) {}
|
|
597
597
|
}
|
|
598
598
|
};
|
|
599
|
-
})();`}a(
|
|
600
|
-
`)}a(
|
|
599
|
+
})();`}a(mh,"editorRevealListenerScript");function Lo(){let e=JSON.stringify(uh());return[`/* @com55/node-red-ai-access editor sidebar v=${Cn} */`,"(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);"," }","})();",mh(),hh()].join(`
|
|
600
|
+
`)}a(Lo,"renderEditorSidebarJs");function Ud(e=Cn){return`<script>
|
|
601
601
|
/**
|
|
602
602
|
* Thin loader for the AI Access editor sidebar.
|
|
603
603
|
* Logic lives at GET /mcp/editor/ai-access-sidebar.js (Cache-Control: no-store)
|
|
@@ -631,8 +631,8 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
|
|
|
631
631
|
(document.head || document.documentElement).appendChild(s);
|
|
632
632
|
})();
|
|
633
633
|
</script>
|
|
634
|
-
`}a(
|
|
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(
|
|
634
|
+
`}a(Ud,"renderEditorSidebarLoaderHtml");ie();tt();Pe();function Bd(e){return nt[e]??"Request failed"}a(Bd,"safeErrorText");function gh(e,t){let n=W(t,"rest");if(!n)return!1;let r=n.body.code,o={error:Bd(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(gh,"sendMappedFlowError");function qd(e){return async(t,n)=>{try{n.json(await ut(Xe(t,e)))}catch(r){if(gh(n,r))return;n.setHeader("X-Content-Type-Options","nosniff"),n.status(502).json({error:Bd("upstream_error"),code:"upstream_error"})}}}a(qd,"listFlowsHandler");ve();ie();Be();tt();function In(e){return e==null||e===""?{ok:!0,format:"summary"}:e==="summary"||e==="full"?{ok:!0,format:e}:{ok:!1}}a(In,"resolveDiffFormat");function yh(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(yh,"compactLineDiff");function wh(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=yh(e.lines),d={key:e.key,kind:"text",beforeBytes:o,afterBytes:s,lines:i.lines};return i.truncated&&(d.truncated=!0),d}a(wh,"formatTextChange");function bh(e,t){return e.map(n=>n.kind==="text"?wh(n,t):n)}a(bh,"formatPropertyChanges");function zd(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(zd,"formatPresenceText");function gi(e,t){return{summary:e.summary,added:e.added.map(n=>zd(n,t)),removed:e.removed.map(n=>zd(n,t)),modified:e.modified.map(n=>({...n,changes:bh(n.changes,t)}))}}a(gi,"formatSections");function Ln(e,t="summary"){let n=gi(e,t),r={...e,...n,format:t};return e.liveChanges&&(r.liveChanges=gi(e.liveChanges,t)),e.vsCurrent&&(r.vsCurrent=gi(e.vsCurrent,t)),r}a(Ln,"applyDiffFormat");se();ve();_o();function Nn(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}a(Nn,"escapeForDocs");var Wd="https://YOUR-NODE-RED/mcp/rpc";function vh(e,t,n,r=Wd){return`## Also available (prefer when your client can send headers)
|
|
636
636
|
|
|
637
637
|
**Prefer MCP or REST** whenever the AI client can send custom headers and JSON
|
|
638
638
|
bodies \u2014 no URL length limits and no chunked drafts. Use this **GET link
|
|
@@ -701,12 +701,15 @@ curl -sS -X POST "${r}" \\
|
|
|
701
701
|
-H "Accept: application/json, text/event-stream" \\
|
|
702
702
|
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
|
|
703
703
|
\`\`\`
|
|
704
|
-
`}a(
|
|
704
|
+
`}a(vh,"docsAlsoAvailable");function kh(e,t){return`# Node-RED AI Access \u2014 self-contained GET link (get_link)${e}
|
|
705
705
|
|
|
706
706
|
**Git mental model:** a proposal is like a pull request. Human approve = merge;
|
|
707
|
-
reject = close; withdraw = you close your own PR.
|
|
708
|
-
|
|
709
|
-
|
|
707
|
+
reject = close; withdraw = you close your own PR. Revision changes auto-rebase
|
|
708
|
+
open proposals. A \`conflict\` blocks approval until resolved. Recent known
|
|
709
|
+
revisions are accepted on submit; unknown revisions return \`version_mismatch\`.
|
|
710
|
+
Open proposals expire after the configured TTL (default 7 days).
|
|
711
|
+
Use REST POST /proposals/{id}/resolve or MCP resolve_proposal_conflict to
|
|
712
|
+
replace your own open proposal with a resolution based on the current flow. Every access link can see all proposals (\`mine: true|false\`).
|
|
710
713
|
|
|
711
714
|
**Which channel to use:** prefer **MCP** (\`/mcp/rpc\` + mcp Bearer) or **REST**
|
|
712
715
|
(\`/mcp/api/v1\` + rest Bearer) when your client can send headers and JSON \u2014
|
|
@@ -776,7 +779,7 @@ fields at the top level** (no \`ok\` / \`proposal\` wrapper). Read
|
|
|
776
779
|
|
|
777
780
|
Lean by default (no \`payload\` / \`baseFlow\`). Pass
|
|
778
781
|
\`?include=payload,baseFlow\` or \`?include_payload=true\` to include them.
|
|
779
|
-
`}a(
|
|
782
|
+
`}a(kh,"docsIntro");function Ah(){return"## Step-by-step workflow\n\n1. `GET ./status` \u2014 confirm the link works; note `caller.expiresAt` /\n `caller.minutesRemaining`.\n2. `GET ./flows` \u2014 list tabs + current `rev`.\n3. **Layered read (prefer this before loading whole tabs):**\n - `GET ./flows/<id>?summary=1` \u2014 node list with `id`/`type`/`name`/`wires` +\n per-node `bytes` (no field contents).\n - `GET ./search?q=<text>&flow=<id>` \u2014 hits with node id, field, line numbers,\n short snippet (`truncated:true` when the result cap is hit).\n - `GET ./nodes/<id>` \u2014 one full node (`flowId` included when known).\n - `GET ./nodes/<id>/field/<name>?lines=A-B` \u2014 one field (optional 1-based\n line range) plus `totalLines` and `bytes`.\n4. Prefer a **targeted text replace** for small edits inside long string fields\n (`template`, `func`, \u2026) \u2014 unique `find` (or `occurrence`) keeps the diff short:\n\n```\nGET ./propose?flow=<flowId>&rev=<rev>&node=<nodeId>&field=<prop>&find=<text>&replace=<text>&occurrence=<n>&title=<text>\n```\n\n5. Or set whole properties (optional `title` \u226480 chars; `summary` is a title fallback):\n\n```\nGET ./propose?flow=<flowId>&rev=<rev>&node=<nodeId>&set.<prop>=<urlencoded>&title=<text>\n```\n\n6. Or multi-node / add / remove / `replace_text` / full tab via base64url JSON patch:\n\n```\nGET ./propose?flow=<flowId>&rev=<rev>&patch=<base64url-json>&title=<text>\n```\n\n7. Poll `GET ./proposals` / `./proposals/<id>` until approved, rejected, or\n withdrawn. Every token can list/read **all** proposals; each item has\n `mine:true|false`. Filter with `?mine=true` (own) or `?mine=false` (others'),\n `?status=pending,superseded` (comma-separated or repeated), `?flowId=`;\n paginate with `?limit=` (1\u2013200) / `?offset=` (response includes `total`,\n `offset`, `hasMore`, and `nextOffset` when more remain). Invalid filters\n return `400 invalid_filter` naming the parameter. Cite `displayName` +\n `reviewUrl` when updating the user.\n8. Optional: `GET ./proposals/<id>/diff` (default `format=summary` compact\n line hunks + byte sizes; `format=full` for complete text) against the\n **base snapshot** stored at submit. Added/removed nodes are presence blocks\n with a summary sentence; open proposals include `rebase` and `liveChanges`; `vsCurrent` shows the merged deployment when there are no conflicts.\n9. Withdraw your own open (pending or conflict) proposal:\n `GET ./proposals/<id>/withdraw?confirm=1&reason=<text>` (status becomes `withdrawn`,\n not `rejected`). Only the link that created a proposal can withdraw it;\n others get `403 proposal_not_owned`.\n\n**Recommended edit loop:** summary \u2192 search \u2192 read a line range \u2192\n`replace_text` \u2192 propose.\n\nOn submit and on read of an open (pending or conflict) proposal, the response may include\n`conflictingProposals` (overlapping open work; list omits this) with\n`overlappingNodeIds` / `overlappingFields` when detectable \u2014 treat like a\ngit conflict warning; prefer one pending change per flow when possible.\n\n## Node types and palette (read-only)\n\nBefore proposing unfamiliar nodes:\n\n1. `GET ./node-types?q=<text>` \u2014 list installed palette types (filter optional;\n results capped; may include `truncated` / `total`).\n2. `GET ./node-types/<type>` \u2014 detail (`defaults` as JSON, `inputs`/`outputs`,\n `requiredConfigNodeTypes`, help sections). Pass `?help=0` to omit help.\n Types are **case-sensitive** (`Debug` \u2192 did you mean `debug`?).\n3. `GET ./palette/search?q=<text>` \u2014 search the public Node-RED catalogue.\n **Recommend only** \u2014 ask the user to install via **Manage palette**, then\n recheck `./node-types`. This API **never installs** packages.\n Core `node-red` types are **not** in the catalogue \u2014 use `./node-types`.\n\nSubmit/propose with an unknown `node.type` returns `400 unknown_node_type`\nwith `nearest` suggestions \u2014 call node-types / palette search and ask the user\nto install the module, then retry.\n"}a(Ah,"docsWorkflow");function Sh(){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(Sh,"docsUrlTable");function xh(){return`## Propose examples
|
|
780
783
|
|
|
781
784
|
**Recommended for small edits to long fields** \u2014 \`replace_text\` (find must be
|
|
782
785
|
**unique** unless you pass \`occurrence\` as a 1-based index). Creates a normal
|
|
@@ -854,11 +857,11 @@ GET ./propose?flow=tab1&rev=abc123&patch=<base64url>
|
|
|
854
857
|
Node-level proposes create a normal \`replace_flow\` proposal; owners should
|
|
855
858
|
approve with deployment type **\`nodes\`** (plugin default) so only changed
|
|
856
859
|
nodes deploy.
|
|
857
|
-
`}a(
|
|
860
|
+
`}a(xh,"docsProposeExamples");function Th(){return`## Chunked drafts (long HTML / large fields)
|
|
858
861
|
|
|
859
|
-
When the GET URL would exceed **${
|
|
862
|
+
When the GET URL would exceed **${gn} bytes**, do not
|
|
860
863
|
stuff everything into \`propose\`. Use drafts (expire after **30 minutes**,
|
|
861
|
-
max **${
|
|
864
|
+
max **${K}** decoded bytes):
|
|
862
865
|
|
|
863
866
|
### Recommended chunk size
|
|
864
867
|
|
|
@@ -903,7 +906,7 @@ Or a full \`replace_flow\` document. JSON-mode drafts **may add nodes**, so
|
|
|
903
906
|
\`draft/new\` does not require an existing \`node\`/\`field\`. Use JSON mode for
|
|
904
907
|
structural changes; use field-mode only when you need to replace one whole
|
|
905
908
|
string field that is too large for a single propose URL.
|
|
906
|
-
`}a(
|
|
909
|
+
`}a(Th,"docsDrafts");function Rh(){return`## Last resort: content larger than chunked drafts
|
|
907
910
|
|
|
908
911
|
If the field is still too large for chunked drafts (decoded size over the
|
|
909
912
|
proposal cap), use this fallback only after drafts fail:
|
|
@@ -917,9 +920,9 @@ proposal cap), use this fallback only after drafts fail:
|
|
|
917
920
|
**Trade-off:** pasted content is **not** reviewed through the proposal diff.
|
|
918
921
|
Prefer \`replace_text\` or chunked drafts whenever they fit so the human can
|
|
919
922
|
still see a readable change on \`/mcp\`.
|
|
920
|
-
`}a(
|
|
923
|
+
`}a(Rh,"docsLastResortPaste");function Ph(){return`## Size cap and REST/MCP nodes payload shapes
|
|
921
924
|
|
|
922
|
-
The proposal JSON cap is **${
|
|
925
|
+
The proposal JSON cap is **${K} bytes**
|
|
923
926
|
(\`MAX_PROPOSAL_JSON_BYTES\` / ~1.5MB) on **every** channel: GET propose/drafts,
|
|
924
927
|
REST \`POST /proposals\`, and MCP \`submit_proposal\`. Oversized bodies return
|
|
925
928
|
\`413 payload_too_large\` (never truncated).
|
|
@@ -963,7 +966,7 @@ Example (canonical):
|
|
|
963
966
|
**Alternative (also accepted):** top-level \`nodes\` array as an alias for
|
|
964
967
|
\`payload.nodes\` (same op shapes). Full-tab replace remains supported via
|
|
965
968
|
\`payload.flow\` (from \`GET ./flows/<id>\` / REST \`GET /flows/{id}\`).
|
|
966
|
-
`}a(
|
|
969
|
+
`}a(Ph,"docsRestNodesPayload");function _h(){return`## Known issues
|
|
967
970
|
|
|
968
971
|
- **Rate limits are per token (and per IP).** Each access-link token has its own
|
|
969
972
|
token bucket. Several tokens can each use the full per-token limit at the same
|
|
@@ -971,20 +974,20 @@ Example (canonical):
|
|
|
971
974
|
- **\`RateLimit-*\` headers describe the per-token bucket** (default
|
|
972
975
|
\`RateLimit-Policy: 300;w=60\`) on token requests; requests without a token
|
|
973
976
|
report the per-IP bucket (\`600;w=60\`). Both are enforced.
|
|
974
|
-
`}a(
|
|
977
|
+
`}a(_h,"docsKnownIssues");function Eh(){return`## Error codes (JSON unless docs)
|
|
975
978
|
|
|
976
|
-
${
|
|
979
|
+
${Fd()}
|
|
977
980
|
|
|
978
981
|
All JSON responses use \`Cache-Control: no-store\`. Never put secrets in query
|
|
979
982
|
strings you will paste publicly beyond this temporary link.
|
|
980
|
-
`}a(
|
|
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=
|
|
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(
|
|
983
|
+
`}a(Eh,"docsErrorsAndAlso");function Gd(e){let{link:t,openapiPath:n,restApiBase:r,mcpRpcPath:o,publicOrigin:s}=e,i=Nn(t.expiresAt),d=t.label?` (${Nn(t.label)})`:"",l=Nn(n),c=Nn(r),u=Nn(o);return[kh(d,i),vh(c,l,u,s?Nn(`${s}${o}`):Wd),Ah(),Sh(),xh(),Ph(),Th(),Rh(),_h(),Eh()].join(`
|
|
984
|
+
`)}a(Gd,"renderGetLinkDocs");var rl=require("crypto");se();ie();tt();zt();Fe();et();ie();function No(e){return{error:e instanceof Error&&e.message.trim()?e.message:"Internal server error",code:"internal_error"}}a(No,"internalErrorBody");function Ch(e,t){e.headersSent||e.status(500).json(No(t))}a(Ch,"sendInternalError");function B(e,t){if(e.headersSent)return;let n=W(t);if(n){e.status(n.statusCode).json(n.body);return}Ch(e,t)}a(B,"sendCaughtRouteError");function O(e){return(t,n,r)=>{Promise.resolve().then(()=>e(t,n,r)).catch(o=>{if(n.headersSent){r(o instanceof Error?o:new Error(String(o)));return}B(n,o)})}}a(O,"asyncRoute");var Oo=require("crypto");se();ve();var G=class extends Error{static{a(this,"DraftValidationError")}statusCode=400;code;constructor(t,n="draft_invalid"){super(t),this.name="DraftValidationError",this.code=n}},Qt=class extends Error{static{a(this,"DraftNotFoundError")}statusCode=404;code="draft_not_found";constructor(t){super(`Draft '${t}' was not found or has expired`),this.name="DraftNotFoundError"}},en=class extends Error{static{a(this,"DraftAlreadySubmittedError")}statusCode=409;code="draft_already_submitted";proposalId;constructor(t,n){super(`Draft '${t}' was already submitted as proposal '${n}'`),this.name="DraftAlreadySubmittedError",this.proposalId=n}},Ih=/^[A-Za-z0-9_-]+={0,2}$/;function Kd(e){if(!Ih.test(e)||e.replace(/=+$/,"").length===0)throw new G("data must be non-empty base64url (A-Z, a-z, 0-9, -, _)","invalid_chunk_data");let t;try{t=Buffer.from(e,"base64url")}catch{throw new G("data must be non-empty base64url (A-Z, a-z, 0-9, -, _)","invalid_chunk_data")}if(t.byteLength===0&&e.length>0)throw new G("data must be non-empty base64url (A-Z, a-z, 0-9, -, _)","invalid_chunk_data");return t}a(Kd,"assertValidBase64UrlChunk");var Fo=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,Oo.randomBytes)(12).toString("hex")),this.maxBytes=t.maxBytes??K,this.ttlMs=t.ttlMs??ca}purgeExpired(t){for(let[n,r]of this.drafts)r.expiresAtMs<=t&&this.drafts.delete(n);for(let[n,r]of this.submitted)r.expiresAtMs<=t&&this.submitted.delete(n)}create(t){let n=this.now();this.purgeExpired(n);let r=this.randomId(),o={id:r,linkId:t.linkId,flowId:t.flowId,expectedRev:t.expectedRev,mode:t.mode,chunks:new Map,createdAtMs:n,expiresAtMs:n+this.ttlMs,totalBytes:0};return t.nodeId!==void 0&&(o.nodeId=t.nodeId),t.field!==void 0&&(o.field=t.field),t.targets!==void 0&&(o.targets=t.targets),t.summary!==void 0&&(o.summary=t.summary),t.title!==void 0&&(o.title=t.title),this.drafts.set(r,o),o}getForLink(t,n){let r=this.now();this.purgeExpired(r);let o=this.submitted.get(t);if(o?.linkId===n)throw new en(t,o.proposalId);let s=this.drafts.get(t);if(s?.linkId!==n)throw new Qt(t);if(s.expiresAtMs<=r)throw new Qt(t);return s}markSubmitted(t,n,r){let o=this.now();this.drafts.delete(t),this.submitted.set(t,{linkId:n,proposalId:r,expiresAtMs:o+this.ttlMs})}append(t,n,r,o){if(!Number.isInteger(r)||r<0)throw new G("seq must be a non-negative integer","invalid_seq");if(typeof o!="string")throw new G("data must be a base64url string","invalid_chunk");let s=this.getForLink(t,n),i=s.chunks.get(r);if(i!==void 0){if(i!==o)throw new G(`Chunk seq ${r} already stored with different data`,"chunk_conflict")}else{let u=Kd(o),p=s.totalBytes+u.byteLength;if(p>this.maxBytes)throw new G(`Draft exceeds limit of ${this.maxBytes} bytes (decoded)`,"payload_too_large");s.chunks.set(r,o),s.totalBytes=p}let d=[...s.chunks.keys()].sort((u,p)=>u-p),l=[],c=d.at(-1);if(c!==void 0)for(let u=0;u<=c;u+=1)s.chunks.has(u)||l.push(u);return{draft:s,receivedSeqs:d,missingSeqs:l,totalBytes:s.totalBytes,maxBytes:this.maxBytes}}getMaxBytes(){return this.maxBytes}assembleAndVerify(t,n,r){let o=this.getForLink(t,n);if(o.chunks.size===0)throw new G("Draft has no chunks","draft_empty");let i=[...o.chunks.keys()].sort((h,m)=>h-m).at(-1);if(i===void 0)throw new G("Draft has no chunks","draft_empty");let d=[];for(let h=0;h<=i;h+=1)o.chunks.has(h)||d.push(h);if(d.length>0)throw new G(`Missing chunk seqs: ${d.join(",")}`,"missing_chunks");let l=[];for(let h=0;h<=i;h+=1){let m=o.chunks.get(h);if(m===void 0)throw new G(`Missing chunk seqs: ${h}`,"missing_chunks");l.push(m)}let c=l.map(h=>Kd(h)),u=Buffer.concat(c);if(u.byteLength>this.maxBytes)throw new G(`Assembled draft exceeds limit of ${this.maxBytes} bytes`,"payload_too_large");let p=r.trim().toLowerCase();if(!/^[0-9a-f]{64}$/.test(p))throw new G("invalid_sha256: expected 64 hex chars","invalid_sha256");if((0,Oo.createHash)("sha256").update(u).digest("hex")!==p)throw new G("sha256 does not match assembled draft content","draft_hash_mismatch");return{draft:o,assembled:u.toString("utf8")}}size(){return this.purgeExpired(this.now()),this.drafts.size}clear(){this.drafts.clear(),this.submitted.clear()}};Ao();$t();ve();var Et=zn(require("express-rate-limit"),1);var wi=6e4,Jd=600,Vd=300;function yi(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?Math.max(1,Math.floor(e)):t}a(yi,"positiveInt");function pt(e){if(!e)return{windowMs:wi,maxPerIp:Jd,maxPerToken:Vd};let t=e;return{windowMs:yi(t.windowMs??t.rateLimitWindowMs,wi),maxPerIp:yi(t.maxPerIp??t.rateLimitMaxPerIp,Jd),maxPerToken:yi(t.maxPerToken??t.rateLimitMaxPerToken,Vd)}}a(pt,"resolveRateLimitConfig");function Lh(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(Lh,"retryAfterSeconds");function Yd(e,t,n=wi){let r=Lh(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(Yd,"sendRateLimited");function Nh(e){return(t,n,r,o)=>{Yd(t,n,e)}}a(Nh,"rateLimitedHandler");function $o(e){return`ip:${(0,Et.ipKeyGenerator)(e.ip??"unknown")}`}a($o,"clientIpKey");function Mo(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(Mo,"accessTokenRateLimitKey");function Xd(e,t,n,r){let o={windowMs:t,max:e,standardHeaders:!0,legacyHeaders:!1,keyGenerator:n,handler:Nh(t)};return r&&(o.skip=r),(0,Et.default)(o)}a(Xd,"createLimiter");function bi(e){let t=pt(e);return Xd(t.maxPerIp,t.windowMs,$o)}a(bi,"createAiAccessRateLimiter");function Fh(e){let t=pt(e);return Xd(t.maxPerToken,t.windowMs,n=>Mo(n)??$o(n),n=>Mo(n)==null)}a(Fh,"createAiAccessTokenRateLimiter");function Zd(e){let t=pt(e),n=bi(t),r=Fh(t);return{ip:n,token:r,stack:[n,r],config:t}}a(Zd,"createAiAccessDualRateLimiters");function Qd(e){let t=pt(e()).windowMs,n=pt(e()).maxPerIp,r=pt(e()).maxPerToken,o=new Et.MemoryStore,s=new Et.MemoryStore,i=a(()=>{let m=pt(e());if(m.windowMs!==t||m.maxPerIp!==n||m.maxPerToken!==r){let g=m.windowMs!==t;t=m.windowMs,n=m.maxPerIp,r=m.maxPerToken,o.resetAll(),s.resetAll(),g&&(o.init?.({windowMs:t}),s.init?.({windowMs:t}))}return m},"refreshWindow"),d=new WeakSet,l=a(m=>{if(d.has(m))return;d.add(m);let g=m.setHeader.bind(m);m.setHeader=((y,A)=>(y.toLowerCase()==="ratelimit-policy"&&typeof A=="string"&&(A=A.replace(/;w=\d+/,`;w=${Math.ceil(t/1e3)}`)),g(y,A)))},"useLiveWindowPolicy"),c=a((m,g)=>(0,Et.default)({store:g,windowMs:t,limit:a(()=>{let y=pt(e());return m?y.maxPerToken:y.maxPerIp},"limit"),standardHeaders:!0,legacyHeaders:!1,keyGenerator:m?y=>Mo(y)??$o(y):$o,skip:a(y=>m&&Mo(y)==null,"skip"),handler:a((y,A,x)=>{Yd(y,A,pt(e()).windowMs)},"handler")}),"make"),u=c(!1,o),p=c(!0,s);return{ip:a((m,g,y)=>(i(),l(g),u(m,g,y)),"ip"),token:a((m,g,y)=>(i(),l(g),p(m,g,y)),"token")}}a(Qd,"createLiveAiAccessDualRateLimiters");ke();function z(e){e.setHeader("Cache-Control","no-store"),e.setHeader("Pragma","no-cache")}a(z,"setNoStore");function vi(e){let t=e.originalUrl||e.url||"",n=ks(t);n!==t&&(Object.defineProperty(e,"originalUrl",{value:n,writable:!0,configurable:!0}),typeof e.url=="string"&&Object.defineProperty(e,"url",{value:ks(e.url),writable:!0,configurable:!0}))}a(vi,"redactTokenFromRequestUrl");function el(e,t){return(n,r,o)=>{z(r);let s=n.originalUrl||n.url||"";if(n.getLinkUrlBytes=Buffer.byteLength(s,"utf8"),vi(n),!Ce(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",ce),r.status(401).json({error:xe,code:qe,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"){tr(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",ce),r.status(401).json({error:Ot,code:er,hint:c});return}if(l.link.kind!=="get_link"){nr(r,"get_link",l.link.kind);return}n.accessLink=l.link,n.getLinkToken=d,o()}}a(el,"requireGetLinkToken");function Fn(e,t){let n=e.getLinkUrlBytes;return(typeof n=="number"?n:Buffer.byteLength(e.originalUrl||e.url||"","utf8"))>gn?(z(t),t.status(414).json({error:`Request URL exceeds ${gn} 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:gn}),!0):!1}a(Fn,"rejectOversizedGetUrl");function j(e,t,n,r,o,s){z(e),e.status(t).json({error:n,code:r,...o?{hint:o}:{},...s})}a(j,"jsonError");function tl(){return(e,t)=>{vi(e),z(t),t.status(404).json({error:"Not found",code:"not_found"})}}a(tl,"getLinkNotFoundHandler");var Oh="link_create_not_supported";function nl(){return(e,t)=>{vi(e),z(t),t.status(403).json({error:"GET access links cannot create tokens",code:Oh,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(nl,"getLinkMintNotSupportedHandler");function M(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(M,"queryString");function He(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(He,"draftErrorHint");async function $h(e,t,n){let r;try{r=await e.getFlow(t)}catch(s){let i=W(s);if(i)return{code:i.body.code,message:i.body.error};throw s}let o=[...Array.isArray(r.flow.nodes)?r.flow.nodes:[],...Array.isArray(r.flow.configs)?r.flow.configs:[]];for(let s of n){let i=o.find(d=>d.id===s.nodeId);if(!i)return{code:"node_not_found",message:`Node '${s.nodeId}' was not found in flow '${t}'`};if(!(s.field in i))return{code:"field_not_found",message:`Field '${s.field}' was not found on node '${s.nodeId}'`}}return null}a($h,"validateFieldDraftTargets");function Mh(e,t,n,r){if(r)try{let o=vr(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=He("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(Mh,"planDraftMode");async function Dh(e,t,n,r){try{let o=await $h(t,n,r);if(o){let s=o.code==="node_not_found"||o.code==="field_not_found"?404:400;return j(e,s,o.message,o.code,He(o.code)),!0}}catch(o){let s=W(o);return s?(j(e,s.statusCode,s.body.error,s.body.code,He(s.body.code)),!0):(B(e,o),!0)}return!1}a(Dh,"rejectInvalidFieldTargets");function ol(e,t){return async(n,r)=>{if(Fn(n,r))return;z(r);let o=M(n.query,"flow"),s=M(n.query,"rev"),i=M(n.query,"node"),d=M(n.query,"field"),l=M(n.query,"summary"),c=M(n.query,"title")??l,u=M(n.query,"mode"),p=M(n.query,"targets");if(!o||!s){j(r,400,"flow and rev query parameters are required","missing_draft_params",He("missing_draft_params"));return}if(!n.accessLink){j(r,401,"Access link required","access_link_missing");return}try{let m=await(await Ar(t,s)).getFlow(o);if(m.rev!==s){r.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:s,currentRev:m.rev,hint:He("version_mismatch")});return}}catch(m){let g=W(m);if(g){j(r,g.statusCode,g.body.error,g.body.code,g.body.code==="flow_not_found"?we("flow_not_found","get")??"Use an id from GET ./flows":He(g.body.code));return}B(r,m);return}let f=Mh(i,d,u,p);if(!f.ok){j(r,f.status,f.error,f.code,f.hint);return}if(f.mode==="field"){let m=f.targets&&f.targets.length>0?f.targets:i&&d?[{nodeId:i,field:d}]:[];if(m.length>0&&await Dh(r,t,o,m))return}let h=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:h.id,mode:h.mode,expiresAt:new Date(h.expiresAtMs).toISOString(),maxBytes:K,hint:"Upload base64url chunks (~4\u20135 KB raw each; base64 adds ~33%; keep URL under ~8 KB) with GET \u2026/draft/<draftId>/append?seq=N&data=\u2026 then submit with sha256."})}}a(ol,"getLinkDraftNewHandler");function sl(e){return(t,n)=>{if(Fn(t,n))return;z(n);let r=t.params.draftId,o=M(t.query,"seq"),s=M(t.query,"data");if(typeof r!="string"||!r){j(n,400,"Missing draft id","missing_draft_id");return}if(!t.accessLink){j(n,401,"Access link required","access_link_missing");return}if(o===void 0||s===void 0){j(n,400,"seq and data query parameters are required","missing_chunk_params","data must be base64url of the raw chunk bytes.");return}let i=Number(o);try{let d=e.append(r,t.accessLink.id,i,s);n.json({ok:!0,draftId:r,receivedSeqs:d.receivedSeqs,missingSeqs:d.missingSeqs,totalBytes:d.totalBytes,maxBytes:d.maxBytes,hint:d.missingSeqs.length===0?"No gaps so far; call submit when all chunks are uploaded":`Upload missing seqs: ${d.missingSeqs.join(",")}`})}catch(d){if(d instanceof Qt||d instanceof en||d instanceof G){j(n,d.statusCode,d.message,d.code,He(d.code),d instanceof en?{proposalId:d.proposalId}:void 0);return}B(n,d);return}}}a(sl,"getLinkDraftAppendHandler");function Hh(e,t){let n=[];if(e.targets&&e.targets.length>0){let r;try{r=JSON.parse(t)}catch{let o=e.targets[0];if(e.targets.length===1&&o)return[{nodeId:o.nodeId,set:{[o.field]:t}}];throw new G("Multi-target draft body must be JSON","invalid_draft_body")}if(Array.isArray(r)){for(let o of r){let s=o;if(!s.nodeId||!s.field)throw new G("Invalid multi-target entry","invalid_draft_body");n.push({nodeId:s.nodeId,set:{[s.field]:s.value}})}return n}if(r&&typeof r=="object"){let o=r;for(let s of e.targets){let i=`${s.nodeId}.${s.field}`;if(!(i in o)&&!(s.field in o))throw new G(`Missing value for ${i} in draft JSON`,"invalid_draft_body");n.push({nodeId:s.nodeId,set:{[s.field]:i in o?o[i]:o[s.field]}})}return n}throw new G("Invalid multi-target draft JSON","invalid_draft_body")}if(e.nodeId&&e.field)return[{nodeId:e.nodeId,set:{[e.field]:t}}];throw new G("Field draft missing node/field","invalid_draft")}a(Hh,"editsFromFieldDraft");function il(e,t,n,r,o,s){return async(i,d)=>{if(Fn(i,d))return;z(d);let l=i.params.draftId,c=M(i.query,"sha256"),u=M(i.query,"title");if(typeof l!="string"||!l){j(d,400,"Missing draft id","missing_draft_id");return}if(!i.accessLink){j(d,401,"Access link required","access_link_missing");return}if(!c){j(d,400,"sha256 query parameter is required","missing_sha256","Pass sha256 hex of the assembled UTF-8 bytes (before base64url).");return}try{let{draft:p,assembled:f}=e.assembleAndVerify(l,i.accessLink.id,c),h=p.flowId,m,g=p.expectedRev,y=await Ar(r,p.expectedRev);if(p.mode==="field"){let k=Hh(p,f),_=await br(y,p.flowId,k);if(_.expectedRev!==p.expectedRev){d.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:p.expectedRev,currentRev:_.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}m=_.flow,g=_.expectedRev}else{let k=kr(JSON.parse(f),p.flowId);if(h=k.flowId,k.kind==="replace_flow")m={...k.flow,id:k.flow.id||h,type:k.flow.type??"tab",nodes:k.flow.nodes??[]};else{let _=await Gt(y,h,k.ops,{expectedRev:p.expectedRev,...s?{nodeTypes:s}:{}});if(_.expectedRev!==p.expectedRev){d.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:p.expectedRev,currentRev:_.expectedRev,draftDeleted:!1,hint:"Draft was kept. Re-read the flow and start a new draft with the current rev."});return}m=_.flow,g=_.expectedRev}}let A=u??p.title??p.summary,x=V(i,o),$=ge(i.query),L=await Tt(t,n,i.accessLink,{type:"replace_flow",expectedRev:g,payload:{flowId:h,flow:m},...A?{title:A}:{}},r,x,s);e.markSubmitted(l,i.accessLink.id,L.id);let D={...fe(L,{...x,include:$}),mine:!0},H=ne(t),T=Ve(t,t.get(L.id),i.accessLink.id);d.status(201).json({ok:!0,proposal:D,sha256:(0,rl.createHash)("sha256").update(Buffer.from(f,"utf8")).digest("hex"),...Ye(T),...ue(H),hint:`Waiting for human approval. Tell the user about "${D.displayName}" (${D.reviewUrl}). Poll GET \u2026/proposals/<id> for status.`})}catch(p){if(p instanceof en){j(d,p.statusCode,p.message,p.code,He(p.code),{proposalId:p.proposalId});return}if(p instanceof Qt||p instanceof G){j(d,p.statusCode,p.message,p.code,He(p.code));return}if(p instanceof SyntaxError){j(d,400,"Assembled draft is not valid JSON","invalid_draft_body");return}let f=W(p);if(f){d.status(f.statusCode).json({...f.body,...He(f.body.code)?{hint:He(f.body.code)}:{}});return}B(d,p);return}}}a(il,"getLinkDraftSubmitHandler");ie();tt();_o();zt();Fe();et();Ao();var jh={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 ki(e,t){return we(e,"get",{cause:t})??jh[e]??Tr(e)}a(ki,"proposeErrorHint");function Uh(e,t,n){e.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:t,currentRev:n,hint:ki("version_mismatch")})}a(Uh,"sendVersionMismatch");async function Bh(e,t,n,r,o){let s=kr(vr(t),n),i=s.flowId;if(s.kind==="replace_flow")return{ok:!0,builtFlowId:i,builtFlow:{...s.flow,id:s.flow.id||i,type:s.flow.type??"tab",nodes:s.flow.nodes??[]},expectedRev:r};let d=await Gt(e,i,s.ops,{expectedRev:r,...o?{nodeTypes:o}:{}});return d.expectedRev!==r?{ok:!1,kind:"version_mismatch",expectedRev:r,currentRev:d.expectedRev}:{ok:!0,builtFlowId:i,builtFlow:d.flow,expectedRev:d.expectedRev}}a(Bh,"buildFromPatch");async function qh(e,t,n,r,o){let s=await br(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(qh,"buildFromSetProps");async function zh(e,t,n,r,o,s,i,d){let l=await Gt(e,t,[{op:"replace_text",nodeId:n,field:r,find:o,replace:s,...i!==void 0?{occurrence:i}:{}}]);return l.expectedRev!==d?{ok:!1,kind:"version_mismatch",expectedRev:d,currentRev:l.expectedRev}:{ok:!0,builtFlowId:t,builtFlow:l.flow,expectedRev:l.expectedRev}}a(zh,"buildFromReplaceText");function Wh(e){let t=Object.prototype.hasOwnProperty.call(e.query,"replace");return{flowId:M(e.query,"flow"),rev:M(e.query,"rev"),nodeId:M(e.query,"node"),patchEncoded:M(e.query,"patch"),setProps:ii(e.query),findText:M(e.query,"find"),replaceText:t?M(e.query,"replace")??"":void 0,fieldName:M(e.query,"field"),occurrenceRaw:M(e.query,"occurrence")}}a(Wh,"readProposeQuery");function je(e,t,n){j(e,400,n,t,ki(t))}a(je,"jsonProposeError");function Gh(e,t){if(t===void 0||t==="")return{ok:!0};let n=Number(t);return Number.isFinite(n)?{ok:!0,occurrence:Math.trunc(n)}:(je(e,"invalid_occurrence","occurrence must be a positive integer"),{ok:!1})}a(Gh,"parseOccurrenceQuery");async function Kh(e,t,n,r,o){if(n.patchEncoded)return Bh(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 je(e,"conflicting_edits","Cannot combine replace_text (find/replace/field) with set.<prop> in one request"),null;if(!n.flowId)return je(e,"missing_flow_id","flow query parameter is required"),null;if(!n.nodeId)return je(e,"missing_node_id","node query parameter is required"),null;if(!n.fieldName)return je(e,"missing_field","field query parameter is required for replace_text"),null;if(n.findText===void 0||n.findText.length===0)return je(e,"missing_find","find query parameter must be a non-empty string"),null;if(n.replaceText===void 0)return je(e,"invalid_replace","replace query parameter is required (may be empty)"),null;let i=Gh(e,n.occurrenceRaw);return i.ok?zh(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?(je(e,"missing_edits","At least one set.<prop>=value is required"),null):qh(t,n.flowId,n.nodeId,n.setProps,r):(je(e,"missing_node_id","node query parameter is required (or pass patch=)"),null):(je(e,"missing_flow_id","flow query parameter is required"),null)}a(Kh,"buildProposeFromQuery");function al(e,t,n,r,o){return async(s,i)=>{if(Fn(s,i))return;z(i);let d=Wh(s),l=M(s.query,"summary"),c=M(s.query,"title")??l;if(!d.rev){je(i,"missing_expected_rev","rev query parameter is required");return}if(!s.accessLink){j(i,401,"Access link required","access_link_missing");return}try{let u=await Ar(n,d.rev),p=await Kh(i,u,d,d.rev,o);if(!p)return;if(!p.ok){Uh(i,p.expectedRev,p.currentRev);return}let f=V(s,r),h=ge(s.query),m=await Tt(e,t,s.accessLink,{type:"replace_flow",expectedRev:p.expectedRev,payload:{flowId:p.builtFlowId,flow:p.builtFlow},...c?{title:c}:{}},n,f,o),g={...fe(m,{...f,include:h}),mine:!0},y=ne(e),A=Ve(e,e.get(m.id),s.accessLink.id),x=m.warnings;i.status(201).json({ok:!0,proposal:g,...l?{summary:l}:{},...Ye(A),...ue(y),...x&&x.length>0?{warnings:x}:{},hint:`Waiting for human approval. Tell the user about "${g.displayName}" (${g.reviewUrl}). Poll GET \u2026/proposals/<id> for status.${x&&x.length>0?` Soft warnings (${x.length}) are non-blocking \u2014 configure later if needed.`:""}`})}catch(u){let p=W(u,"get");if(p){let f=ki(p.body.code,p.body.cause);i.status(p.statusCode).json({...p.body,...f?{hint:f}:{}});return}B(i,u)}}}a(al,"getLinkProposeHandler");ie();tt();ie();var Jh=50,dl=40,Vh=20;function Yh(e){return Buffer.byteLength(JSON.stringify(e),"utf8")}a(Yh,"nodeBytes");function Xh(e){let t={id:e.id,type:e.type,bytes:Yh(e)};return typeof e.name=="string"&&(t.name=e.name),"wires"in e&&(t.wires=e.wires),t}a(Xh,"summarizeNode");async function Do(e,t){let{rev:n,flow:r}=await Me(e,t),o=r,s=Array.isArray(o.nodes)?o.nodes.map(Xh):[],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(Do,"getFlowSummary");function ll(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(ll,"collectTabs");async function cl(e,t,n){if(!t)throw new b("Missing node id","missing_node_id");if(n){let{rev:d,flow:l}=await Me(e,n),c=l,u=Array.isArray(c.nodes)?c.nodes:[],p=Array.isArray(c.configs)?c.configs:[],f=[...u,...p].find(h=>h.id===t);if(!f)throw new b("Node was not found","node_not_found",{statusCode:404});return{rev:d,flowId:n,node:f}}let{rev:r,flows:o}=await ut(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 ll(o)){let l=d.id;if(l)try{let u=(await Me(e,l)).flow,f=[...u.nodes??[],...u.configs??[]].find(h=>h.id===t);if(f){s=f,i=l;break}}catch{}}if(!s)throw new b("Node was not found","node_not_found",{statusCode:404});return{rev:r,flowId:i,node:s}}a(cl,"findNodeInFlows");async function Ho(e,t,n){let r=await cl(e,t,n);return{rev:r.rev,flowId:r.flowId,node:r.node}}a(Ho,"getNodeById");function Zh(e){if(e===void 0||e==="")return;let t=/^(\d+)(?:-(\d+))?$/.exec(e.trim());if(!t)throw new b("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 b("lines must be A or A-B (1-based inclusive; end >= start)","invalid_line_range");if(r<n)throw new b("lines must be A or A-B (1-based inclusive; end >= start)","invalid_line_range");return{start:n,end:r}}a(Zh,"parseLineRange");async function jo(e,t,n,r={}){if(!n)throw new b("Missing field name","missing_field");let o=await cl(e,t,r.flowId);if(!(n in o.node))throw new b("Field was not found on the node","field_not_found");let s=o.node[n];if(typeof s!="string")throw new b("Field is not a string","field_not_string");let i=s.split(`
|
|
985
|
+
`),d=i.length,l=Buffer.byteLength(s,"utf8"),c=Zh(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 b(`lines range is out of bounds (start ${c.start} > ${d} total lines)`,"line_range_out_of_bounds",{details:{totalLines:d}});let u=c.start-1,p=Math.min(c.end,i.length),f=i.slice(u,p).join(`
|
|
986
|
+
`);return{rev:o.rev,flowId:o.flowId,nodeId:t,field:n,value:f,totalLines:d,bytes:l,lineRange:{start:c.start,end:Math.min(c.end,d)}}}a(jo,"getNodeField");function Qh(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(Qh,"stringFields");function em(e,t,n){let r=Math.max(0,t-dl),o=Math.min(e.length,t+n+dl),s=e.slice(r,o).replace(/\s+/g," ");return r>0&&(s=`\u2026${s}`),o<e.length&&(s=`${s}\u2026`),s}a(em,"snippetAround");function tm(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(tm,"lineNumberAtIndex");function Ai(e,t,n,r,o){for(let{field:s,value:i}of Qh(e)){if(r.length>=o)return!0;let d=[],l=new Set,c=-1,u=a((f,h)=>{l.has(f)||(l.add(f),d.push(f)),c<0&&(c=h)},"pushLine");if(!n.includes(`
|
|
984
987
|
`)){let f=i.split(`
|
|
985
|
-
`);for(let
|
|
986
|
-
`)){let f=0;for(;f<=i.length;){let
|
|
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=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[t]??t)}a(He,"esc");var kd={read:'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5c-5 0-9 4.5-10 7 1 2.5 5 7 10 7s9-4.5 10-7c-1-2.5-5-7-10-7Zm0 11.5a4.5 4.5 0 1 1 0-9 4.5 4.5 0 0 1 0 9Zm0-2.3a2.2 2.2 0 1 0 0-4.4 2.2 2.2 0 0 0 0 4.4Z"/></svg>',propose:'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 17.2V20h2.8l8.3-8.3-2.8-2.8L4 17.2Zm13.7-7.6a1 1 0 0 0 0-1.4l-1.9-1.9a1 1 0 0 0-1.4 0l-1.4 1.4 2.8 2.8 1.9-1.9Z"/></svg>'},pm={read:["Read your flows","View flows, nodes, node types and AI Access logs."],propose:["Propose flow changes","Submit change proposals. Nothing is deployed until you approve it in Node-RED."]},fm='<svg class="logo" viewBox="0 0 48 48" aria-hidden="true"><rect width="48" height="48" rx="11" fill="#8f0000"/><path d="M11 15h9a4 4 0 0 1 4 4v10a4 4 0 0 0 4 4h9" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round"/><rect x="6" y="11" width="9" height="8" rx="2.5" fill="#fff"/><rect x="33" y="29" width="9" height="8" rx="2.5" fill="#fff"/><circle cx="37.5" cy="16" r="5" fill="none" stroke="#fff" stroke-width="2.4"/><path d="M37.5 13.5v5M35 16h5" stroke="#fff" stroke-width="2.2" stroke-linecap="round"/></svg>',mm=`
|
|
988
|
+
`);for(let h=0;h<f.length;h+=1){let g=(f[h]??"").indexOf(n);if(g>=0){let y=0;for(let A=0;A<h;A+=1)y+=(f[A]?.length??0)+1;u(h+1,y+g)}}}if(n.includes(`
|
|
989
|
+
`)){let f=0;for(;f<=i.length;){let h=i.indexOf(n,f);if(h<0)break;u(tm(i,h),h),f=h+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,Vh),lineCount:p,snippet:em(i,Math.max(0,c),n.length)})}return r.length>=o}a(Ai,"searchInNode");function ul(e){return[...e.nodes??[],...e.configs??[]]}a(ul,"nodePool");async function nm(e,t,n,r,o){let s=await Me(e,t);for(let i of ul(s.flow))if(Ai(i,t,n,r,o))return{rev:s.rev,truncated:!0};return{rev:s.rev,truncated:!1}}a(nm,"searchOneFlow");async function rm(e,t,n,r){let o=await ut(e),s=new Set;for(let i of ll(o.flows))if(i.id)try{let d=await Me(e,i.id);for(let l of ul(d.flow))if(s.add(l.id),Ai(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(Ai(d,l,t,n,r))return{rev:o.rev,truncated:!0}}return{rev:o.rev,truncated:!1}}a(rm,"searchAllFlows");async function Uo(e,t,n={}){if(typeof t!="string"||!t.trim())throw new b("q query parameter is required","missing_query");let r=typeof n.cap=="number"&&n.cap>0?Math.floor(n.cap):Jh,o=[],s=n.flowId?await nm(e,n.flowId,t,o,r):await rm(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(Uo,"searchFlows");function pl(e){if(e===void 0)return!1;let t=e.toLowerCase();return t==="1"||t==="true"||t==="yes"}a(pl,"isTruthyQueryFlag");Pe();function om(e,t,n){switch(e){case"node_not_found":case"flow_not_found":case"field_not_found":return n??we(e,t);default:return we(e,t)}}a(om,"layeredReadErrorHint");function tn(e){return nt[e]??"Request failed"}a(tn,"safeErrorText");function _e(e,t,n){e.setHeader("X-Content-Type-Options","nosniff"),e.setHeader("Content-Type","application/json; charset=utf-8"),e.status(t).json(n)}a(_e,"sendJson");function Bo(e,t,n,r){let o=W(t,n);if(!o)return!1;let s=o.body.code,i={error:tn(s),code:s};typeof o.body.matchCount=="number"&&(i.matchCount=o.body.matchCount),typeof o.body.totalLines=="number"&&(i.totalLines=o.body.totalLines),typeof o.body.expectedRev=="string"&&(i.expectedRev=o.body.expectedRev),typeof o.body.currentRev=="string"&&(i.currentRev=o.body.currentRev);let d=om(s,n,r);return d&&(i.hint=d),_e(e,o.statusCode,i),!0}a(Bo,"sendMappedError");function qo(e){_e(e,502,{error:tn("upstream_error"),code:"upstream_error"})}a(qo,"sendUpstreamError");function zo(e,t="rest"){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){_e(r,400,{error:tn("missing_flow_id"),code:"missing_flow_id"});return}let s=pl(M(n.query,"summary"));try{if(s){_e(r,200,await Do(Xe(n,e),o));return}_e(r,200,await Me(Xe(n,e),o))}catch(i){if(Bo(r,i,t))return;qo(r)}}}a(zo,"getFlowOrSummaryHandler");function Wo(e,t="rest"){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||!o){_e(r,400,{error:tn("missing_node_id"),code:"missing_node_id"});return}let s=M(n.query,"flow");try{_e(r,200,await Ho(Xe(n,e),o,s))}catch(i){if(Bo(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;qo(r)}}}a(Wo,"getNodeHandler");function Go(e,t="rest"){return async(n,r)=>{let o=n.params.id,s=n.params.name;if(typeof o!="string"||!o){_e(r,400,{error:tn("missing_node_id"),code:"missing_node_id"});return}if(typeof s!="string"||!s){_e(r,400,{error:tn("missing_field"),code:"missing_field"});return}let i=M(n.query,"flow"),d=M(n.query,"lines");try{_e(r,200,await jo(Xe(n,e),o,s,{...i?{flowId:i}:{},...d?{lines:d}:{}}))}catch(l){if(Bo(r,l,t,"Use summary/search to discover node ids and fields."))return;qo(r)}}}a(Go,"getNodeFieldHandler");function Ko(e,t="rest"){return async(n,r)=>{let o=M(n.query,"q");if(!o){_e(r,400,{error:tn("missing_query"),code:"missing_query",hint:t==="get"?"Example: GET \u2026/search?q=TODO&flow=tab1":we("missing_query",t)});return}let s=M(n.query,"flow");try{_e(r,200,await Uo(Xe(n,e),o,s?{flowId:s}:{}))}catch(i){if(Bo(r,i,t))return;qo(r)}}}a(Ko,"searchFlowsHandler");ie();qt();var sm={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 Rr(e){return sm[e]??"Request failed"}a(Rr,"safeErrorText");function Ct(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(Ct,"sendJson");function Si(e,t){let n=W(t);if(!n)return!1;let r=n.body.code,o={error:Rr(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."),Ct(e,n.statusCode,o),!0}a(Si,"sendMappedError");function Jo(e){return async(t,n)=>{try{let r=M(t.query,"q"),o=await ao(e.nodeTypes,r?{q:r}:{});Ct(n,200,o)}catch(r){if(Si(n,r))return;Ct(n,502,{error:Rr("upstream_error"),code:"upstream_error"})}}}a(Jo,"listNodeTypesHandler");function Vo(e){return async(t,n)=>{try{let r=t.params.type,o=typeof r=="string"?decodeURIComponent(r):"",s=M(t.query,"help"),i=!(s==="0"||s==="false"||s==="no"||s==="omit"),d=await Bt(e.nodeTypes,o,{includeHelp:i});Ct(n,200,d)}catch(r){if(Si(n,r))return;Ct(n,502,{error:Rr("upstream_error"),code:"upstream_error"})}}}a(Vo,"getNodeTypeHandler");function Yo(e){return async(t,n)=>{try{let r=M(t.query,"q");if(!r){Ct(n,400,{error:Rr("missing_query"),code:"missing_query",hint:"Pass ?q= to search the Node-RED catalogue (recommend only)."});return}let o=await lo(e.nodeTypes,r,{...e.catalogueFetcher?{catalogueFetcher:e.catalogueFetcher}:{},...e.catalogueUrl?{catalogueUrl:e.catalogueUrl}:{}});Ct(n,200,o)}catch(r){if(Si(n,r))return;Ct(n,502,{error:Rr("upstream_error"),code:"upstream_error"})}}}a(Yo,"searchPaletteHandler");zt();Te();Je();function On(e,t){return e[t]??""}a(On,"lineAt");function Pr(e,t,n){let r=e[t];return r?r[n]??0:0}a(Pr,"cell");function fl(e,t,n,r){let o=e[t];o&&(o[n]=r)}a(fl,"setCell");function ml(e,t){let n=hl(e),r=hl(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)On(n,u)===On(r,p)?fl(i,u,p,Pr(i,u+1,p+1)+1):fl(i,u,p,Math.max(Pr(i,u+1,p),Pr(i,u,p+1)));let d=[],l=0,c=0;for(;l<o&&c<s;){let u=On(n,l),p=On(r,c);u===p?(d.push({op:"equal",text:u}),l+=1,c+=1):Pr(i,l+1,c)>=Pr(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:On(n,l)}),l+=1;for(;c<s;)d.push({op:"add",text:On(r,c)}),c+=1;return d}a(ml,"diffLines");function hl(e){return e.length===0?[]:e.split(`
|
|
990
|
+
`)}a(hl,"splitLines");Fe();yr();et();Pe();var yl=new Set(["func","template","info","payload","code","text","format","syntax"]),im=new Set(["x","y","w","h","wires","g"]),am=new Set(["w","h","g"]);function nn(e){if(typeof e.name=="string"&&e.name.length>0)return e.name;if(typeof e.label=="string"&&e.label.length>0)return e.label}a(nn,"nodeName");function gl(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(gl,"collectNodes");function es(e){if(e===void 0)return"undefined";if(e===null||typeof e!="object")return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(es).join(",")}]`;let t=e;return`{${Object.keys(t).sort().map(r=>`${JSON.stringify(r)}:${es(t[r])}`).join(",")}}`}a(es,"stableStringify");function wl(e,t){return es(e)===es(t)}a(wl,"valuesEqual");function ts(e){return typeof e=="string"?e:null}a(ts,"asText");function dm(e){return/<\/?[a-z][\s\S]*>/i.test(e.slice(0,800))}a(dm,"looksLikeHtml");function lm(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:dm(n)}a(lm,"isHtmlTemplateField");function bl(e,t,n){let r=ts(n);if(yl.has(t)&&r!==null){let o={key:t,kind:"text",value:r};return lm(e,t,r)&&(o.preview="html"),o}return{key:t,kind:"scalar",value:n===void 0?null:n}}a(bl,"toPresenceProperty");function cm(e,t){let n=[];for(let r of[...Object.keys(e)].sort())if(!(am.has(r)||r==="id"||r==="z")){if(r==="wires"){let o=vl(e,t);n.push({key:"wires",kind:"scalar",value:o.length>0?o:e.wires??null});continue}n.push(bl(e,r,e[r]))}return n}a(cm,"nodeProperties");function vl(e,t){let n=e.wires;if(!Array.isArray(n)||n.length===0)return[];let r=[];return n.forEach((o,s)=>{if(!Array.isArray(o))return;let i=[];for(let d of o){if(typeof d!="string"||!d)continue;let l=t.get(d);i.push({id:d,name:l?nn(l)??l.type:d})}r.push({port:s,to:i})}),r}a(vl,"describeWiresStructured");var um=new Set(["func","template","code","text"]);function pm(e,t){let n=[],r=nn(e);r&&n.push({key:"name",kind:"scalar",value:r});let o=vl(e,t);o.length>0&&n.push({key:"wires",kind:"scalar",value:o});for(let s of um){if(!(s in e))continue;let i=ts(e[s]);i===null||i.length===0||n.push(bl(e,s,i))}return n}a(pm,"buildKeyFields");function fm(e,t,n){let r=wt(e.type),o=nn(e),s=o?` "${o}"`:"",i=t?` in "${t}"`:"",d=n===1?"1 field":`${n} fields`;return`Changes ${r} node${s}${i} (${d})`}a(fm,"buildModifiedSummary");function hm(e,t,n){let r=wt(e.type),o=nn(e),s=t==="added"?"Adds":"Removes",i=t==="added"?"to":"from",d=o?` ${JSON.stringify(o)}`:"",l=n?` ${i} flow ${JSON.stringify(n)}`:"",c=`${s} ${r} node${d}${l}`;if(t==="added"&&r==="template"){let u=typeof e.field=="string"&&e.field?e.field:"";u&&(c+=`, output to msg.${u}`)}return c}a(hm,"buildPresenceSummary");function Xo(e,t,n,r){let o={id:e.id,type:e.type,summary:hm(e,t,n),keyFields:pm(e,r),properties:cm(e,r)},s=nn(e);return s!==void 0&&(o.name=s),o}a(Xo,"toPresenceNode");function mm(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]),r=[];for(let o of[...n].sort()){if(im.has(o)||o==="id"||o==="z")continue;let s=e[o],i=t[o];if(wl(s,i))continue;let d=ts(s),l=ts(i);if(yl.has(o)&&(d!==null||l!==null)){let c=d??"",u=l??"";r.push({key:o,kind:"text",before:c,after:u,lines:ml(c,u)});continue}r.push({key:o,kind:"scalar",before:s===void 0?null:s,after:i===void 0?null:i})}return r}a(mm,"compareNodes");function gm(e,t){let n=["label","disabled","info","env"],r=[];for(let o of n)wl(e[o],t[o])||r.push(o);return r}a(gm,"compareTabMeta");function Zo(e){return typeof e.label=="string"&&e.label.trim()?e.label.trim():""}a(Zo,"flowLabelOf");function Qo(e,t){let n=gl(t),r=gl(e),o=Zo(e)||Zo(t),s=Zo(t)||Zo(e),i=[],d=[],l=[];for(let c of e.nodes??[])n.has(c.id)||i.push(Xo(c,"added",o,r));for(let c of e.configs??[])n.has(c.id)||i.push(Xo(c,"added",o,r));for(let c of t.nodes??[])r.has(c.id)||d.push(Xo(c,"removed",s,n));for(let c of t.configs??[])r.has(c.id)||d.push(Xo(c,"removed",s,n));for(let[c,u]of r){let p=n.get(c);if(!p)continue;let f=mm(p,u);if(f.length>0){let h={id:c,type:u.type||p.type,summary:fm(u,o||s,f.length),changes:f},m=nn(u)??nn(p);m!==void 0&&(h.name=m),l.push(h)}}return l.sort((c,u)=>c.id.localeCompare(u.id)),{summary:{added:i.length,removed:d.length,modified:l.length,tabChanges:gm(t,e),beforeNodeCount:n.size,afterNodeCount:r.size},added:i,removed:d,modified:l}}a(Qo,"buildDiffSections");function xi(e,t,n={}){let r=Qo(e.payload.flow,t.flow),o=n.currentRev??t.rev,s=e.expectedRev,i=n.baseRevStale??(U(e.status)&&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(xi,"buildProposalDiff");async function $n(e,t,n=e){let r=e.payload.flowId,o={id:r,type:"tab",nodes:[],configs:[]},s=e.expectedRev,i=o;try{let u=await t.getFlow(r);s=u.rev,i=u.flow}catch(u){if(!(u instanceof X))throw u;try{s=(await t.getFlows()).rev}catch{}}let d=U(e.status),l=d&&e.expectedRev!==s,c=e.baseFlow;if(d){let u=Oe(t),p=await u.getFlows(),f=oi(n,p),h=u!==t,m=h?lt(n):n,g=Wt(p.flows,r,n.baseFlow)??o,y=h?bt(g).value:g,A=xi(m,{rev:e.expectedRev,flow:m.baseFlow??y},{currentRev:p.rev,compareAgainst:m.baseFlow?"base":"current",baseSnapshotMissing:m.baseFlow===void 0,baseRevStale:e.expectedRev!==p.rev});return A.rebase={state:f.state,conflicts:h?Xs(f.conflicts):f.conflicts,...f.mergedHash?{mergedHash:f.mergedHash}:{}},A.liveChanges=Qo(y,m.baseFlow??y),f.state!=="conflict"&&f.merged&&(A.vsCurrent=Qo(h?bt(f.merged).value:f.merged,y)),A}if(c!==void 0){let u=l?Qo(e.payload.flow,i):void 0;return xi(e,{rev:e.expectedRev,flow:c},{currentRev:s,compareAgainst:"base",baseRevStale:l,...u?{vsCurrent:u}:{}})}return xi(e,{rev:s,flow:i},{compareAgainst:"current",baseSnapshotMissing:!0,baseRevStale:l})}a($n,"computeProposalDiff");Fe();et();Be();ke();var kl=require("path");function ns(e){return(0,kl.join)(e.userDir,".ai-access")}a(ns,"getStorageDir");var ym=["status:read","flows:read","proposals:create","proposals:read-own"];function Ti(){return En()}a(Ti,"readPackageVersion");function wm(e,t=Date.now()){let n=Date.parse(e);return Number.isFinite(n)?Math.max(0,Math.ceil((n-t)/1e3)):0}a(wm,"secondsRemainingUntil");function bm(e,t=Date.now()){let n=Date.parse(e);return Number.isFinite(n)?Math.max(0,Math.ceil((n-t)/6e4)):0}a(bm,"minutesRemainingUntil");function vm(e,t){let n=Se(t),r=ht(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(vm,"baseUrlsForTokenKind");function Mn(e,t){let n=Gr(e,t),r=Ee(e.settings);if(t.auth){let o=t.auth.extra?.user;return{ok:!0,version:Ti(),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:Ti(),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:bm(t.accessLink.expiresAt,o),secondsRemaining:wm(t.accessLink.expiresAt,o),permissions:[...ym],baseUrls:vm(t.accessLink.kind,e.settings.httpAdminRoot)}}}return{ok:!0,version:Ti(),adminAuthEnabled:n.adminAuthEnabled,aiAccessEnabled:r.enabled,aiFeaturesEnabled:n.aiFeaturesEnabled,allowWithoutAdminAuth:n.allowWithoutAdminAuth,permission:n.permission,capabilities:n.capabilities,storageDir:ns(e.settings),defaultLinkTtlSeconds:r.defaultLinkTtlSeconds,maxActiveLinks:r.maxActiveLinks,defaultDeploymentType:r.defaultDeploymentType,caller:{kind:"admin",...n.permission?{permission:n.permission}:{}}}}a(Mn,"buildStatus");function Al(e,t){let n=t.draftStore??new Fo,r=t.rateLimiters??Zd(t.rateLimit??e.settings.aiAccess),o=e.httpAdmin,s="/mcp/a/:token",i=[r.ip,el(e,t.linkStore),r.token];o.use("/mcp/a",Hd()),o.get(s,...i,O((u,p)=>{z(p),p.setHeader("X-Content-Type-Options","nosniff");let f=Se(e.settings.httpAdminRoot),h=u.accessLink;if(!h){j(p,401,"Access link required","access_link_missing");return}let m=Gd({link:h,openapiPath:`${f}/openapi.yaml`,restApiBase:f,mcpRpcPath:ht(e.settings.httpAdminRoot),publicOrigin:vs(u)});p.status(200).type("text/markdown; charset=utf-8").send(m)})),o.get(`${s}/status`,...i,O((u,p)=>{z(p),p.json(Mn(e,u))})),o.get(`${s}/flows`,...i,O(async(u,p)=>{z(p);try{p.json(await ut(t.flowAdapter))}catch(f){let h=W(f);if(h){p.status(h.statusCode).json({...h.body,hint:"Retry after the owner fixes the plugin runtime."});return}j(p,502,f instanceof Error?f.message:String(f),"upstream_error")}})),o.get(`${s}/flows/:id`,...i,O(async(u,p)=>{z(p),await zo(t.flowAdapter,"get")(u,p)})),o.get(`${s}/nodes/:id`,...i,O(async(u,p)=>{z(p),await Wo(t.flowAdapter,"get")(u,p)})),o.get(`${s}/nodes/:id/field/:name`,...i,O(async(u,p)=>{z(p),await Go(t.flowAdapter,"get")(u,p)})),o.get(`${s}/search`,...i,O(async(u,p)=>{z(p),await Ko(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,O(async(u,p)=>{z(p),await Jo(d)(u,p)})),o.get(`${s}/node-types/:type`,...i,O(async(u,p)=>{z(p),await Vo(d)(u,p)})),o.get(`${s}/palette/search`,...i,O(async(u,p)=>{z(p),await Yo(d)(u,p)}));let l=e.settings.httpAdminRoot;km(o,s,i,t,l),o.get(`${s}/propose`,...i,O(al(t.proposalStore,t.auditLog,t.flowAdapter,l,t.nodeTypesAdapter))),xm(o,s,i,n,t,l);let c=nl();return o.all(`${s}/links`,c),o.all(`${s}/links/:id`,c),o.use("/mcp/a",tl()),n}a(Al,"registerGetLinkRoutes");function km(e,t,n,r,o){e.get(`${t}/proposals`,...n,O(async(s,i)=>{z(i);let d=s.accessLink;if(!d){j(i,401,"Access link required","access_link_missing");return}let l=V(s,o),c=ge(s.query),u=xo(s.query),p=await Qe(r.flowAdapter),f=p?$e(r.proposalStore,p).expired:ne(r.proposalStore),h=Tn(r.proposalStore,d,l,u);i.json({proposals:h.proposals.map(m=>he(r.proposalStore,d,m,{...l,include:c},{includeConflicts:!1})),...Jt({...h,returned:h.proposals.length}),...ue(f)})})),e.get(`${t}/proposals/:id`,...n,O(async(s,i)=>{z(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){j(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){j(i,401,"Access link required","access_link_missing");return}try{let c=V(s,o),u=ge(s.query),p=await Qe(r.flowAdapter),f=ct(r.proposalStore,l,d,c);i.json(he(r.proposalStore,l,f,{...c,include:u},{...p?{live:p}:{}}))}catch(c){let u=W(c,"get");if(u){i.status(u.statusCode).json({...u.body,hint:u.body.hint??"List proposals with GET \u2026/proposals."});return}B(i,c);return}})),Am(e,t,n,r,o),Sm(e,t,n,r,o)}a(km,"registerGetLinkProposalRoutes");function Am(e,t,n,r,o){e.get(`${t}/proposals/:id/withdraw`,...n,O((s,i)=>{z(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){j(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){j(i,401,"Access link required","access_link_missing");return}let c=s.query.confirm;if(c!=="1"&&c!=="true"){j(i,400,"Withdraw over GET requires confirm=1","confirm_required",we("confirm_required","get"));return}let u=typeof s.query.reason=="string"?s.query.reason:void 0,p=V(s,o),f=ge(s.query);try{let h=Rn(r.proposalStore,r.auditLog,l,d,u,p);i.json(he(r.proposalStore,l,h,{...p,include:f}))}catch(h){let m=W(h,"get");if(m){let g=m.body.code;j(i,m.statusCode,nt[g]??"Request failed",g,we(g,"get"));return}j(i,500,"Internal server error","internal_error");return}}))}a(Am,"registerGetLinkWithdrawRoute");function Sm(e,t,n,r,o){e.get(`${t}/proposals/:id/diff`,...n,O(async(s,i)=>{z(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){j(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){j(i,401,"Access link required","access_link_missing");return}try{let c=ct(r.proposalStore,l,d,V(s,o)),u=M(s.query,"format"),p=In(u==="json"||u==="md"?void 0:u);if(!p.ok){j(i,400,"Invalid diff format","invalid_diff_format","Valid formats: summary, full (default summary).");return}i.json(Ln(await $n(c,r.flowAdapter,r.proposalStore.get(d)),p.format))}catch(c){let u=W(c);if(u){i.status(u.statusCode).json(u.body);return}B(i,c);return}}))}a(Sm,"registerGetLinkDiffRoute");function xm(e,t,n,r,o,s){e.get(`${t}/draft/new`,...n,O(ol(r,o.flowAdapter))),e.get(`${t}/draft/:draftId/append`,...n,O(sl(r))),e.get(`${t}/draft/:draftId/submit`,...n,O(il(r,o.proposalStore,o.auditLog,o.flowAdapter,s,o.nodeTypesAdapter)))}a(xm,"registerGetLinkDraftRoutes");se();var Sl=require("module");function Tm(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,Sl.createRequire)(e)}a(Tm,"createPluginRequire");function Rm(e){try{let r=Tm()("@node-red/runtime")._?.adminApi?.httpAdmin;if(r&&typeof r.use=="function"&&r!==e.httpAdmin)return r}catch{}return null}a(Rm,"resolveEditorHttpAdmin");function xl(e){let t=[e.httpAdmin],n=Rm(e);return n&&t.push(n),t}a(xl,"httpAdminAppsForMcpErrors");var Tl=Symbol.for("com55.ai-access.mcpJsonErrorHandler");function Pm(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(Pm,"mcpRpcMatcher");function Ri(e){return e.includes("/mcp")}a(Ri,"isMcpPath");function _m(e){return e.path||e.url||e.originalUrl||""}a(_m,"requestPath");function Em(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(Em,"isJsonParseError");function Cm(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(Cm,"isPayloadTooLarge");function Im(e){let t=Pm(e);return(n,r,o,s)=>{if(o.headersSent){s(n);return}let i=_m(r);if(Em(n)&&Ri(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(Cm(n)&&Ri(i)){o.status(413).json({error:`Request body exceeds limit of ${K} bytes`,code:"payload_too_large"});return}if(Ri(i)){if(t(i)){o.status(500).json({jsonrpc:"2.0",error:{code:-32603,message:No(n).error,data:{code:"internal_error"}},id:null});return}o.status(500).json(No(n));return}s(n)}}a(Im,"mcpJsonErrorHandler");function Lm(e,t){let n=e;n[Tl]||(n[Tl]=!0,e.use(Im(t)))}a(Lm,"attachOnce");function Rl(e){for(let t of xl(e))Lm(t,e.settings.httpAdminRoot)}a(Rl,"attachMcpJsonErrorHandlers");xr();var rn=class extends Error{static{a(this,"AccessLinkLimitError")}code="access_link_limit";statusCode=409;constructor(t){super(`Cannot create access link: at most ${t} active links are allowed. Revoke an existing link first.`),this.name="AccessLinkLimitError"}},on=class extends Error{static{a(this,"AccessLinkNotFoundError")}code="access_link_not_found";statusCode=404;constructor(t){super(`Access link '${t}' was not found`),this.name="AccessLinkNotFoundError"}},_r=class extends Error{static{a(this,"AccessLinkValidationError")}code="access_link_invalid";statusCode=400;constructor(t){super(t),this.name="AccessLinkValidationError"}},It=class extends Error{static{a(this,"AccessLinkTtlError")}code="invalid_ttl";statusCode=400;hint=Vt;constructor(t){super(t),this.name="AccessLinkTtlError"}};var sn=require("crypto");xr();function Pi(e){return e==="get_link"||e==="rest"||e==="mcp"}a(Pi,"isAccessLinkKind");function _i(e){return e??"get_link"}a(_i,"resolveAccessLinkKind");function Pl(e){return(0,sn.createHash)("sha256").update(e,"utf8").digest("hex")}a(Pl,"hashToken");function Nm(e,t){let n=Buffer.from(e,"utf8"),r=Buffer.from(t,"utf8");return n.length!==r.length?!1:(0,sn.timingSafeEqual)(n,r)}a(Nm,"hashesEqual");function Er(e){let t={id:e.id,kind:_i(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(Er,"toPublic");var an=class{static{a(this,"AccessLinkStore")}links=new Map;defaultTtlSeconds;maxActiveLinks;now;randomId;randomToken;constructor(t={}){this.defaultTtlSeconds=t.defaultTtlSeconds??14400,this.maxActiveLinks=t.maxActiveLinks??10,this.now=t.now??(()=>Date.now()),this.randomId=t.randomId??(()=>(0,sn.randomBytes)(12).toString("hex")),this.randomToken=t.randomToken??(()=>`aia_${(0,sn.randomBytes)(32).toString("base64url")}`)}configureDefaults(t){typeof t.defaultTtlSeconds=="number"&&Number.isFinite(t.defaultTtlSeconds)&&t.defaultTtlSeconds>0&&(this.defaultTtlSeconds=Math.floor(t.defaultTtlSeconds)),typeof t.maxActiveLinks=="number"&&Number.isFinite(t.maxActiveLinks)&&t.maxActiveLinks>0&&(this.maxActiveLinks=Math.floor(t.maxActiveLinks))}isActive(t,n){return t.revokedAtMs!==void 0?!1:t.expiresAtMs>n}list(){let t=this.now();return[...this.links.values()].filter(n=>this.isActive(n,t)).sort((n,r)=>r.createdAtMs-n.createdAtMs).map(Er)}create(t={}){let n=this.now();if([...this.links.values()].filter(p=>this.isActive(p,n)).length>=this.maxActiveLinks)throw new rn(this.maxActiveLinks);let o=this.defaultTtlSeconds;if(t.ttlSeconds!==void 0){if(typeof t.ttlSeconds!="number"||!Number.isFinite(t.ttlSeconds)||t.ttlSeconds<=0)throw new It("ttlSeconds must be a positive number");o=Math.floor(t.ttlSeconds)}let s=typeof t.label=="string"&&t.label.trim().length>0?t.label.trim():void 0,i=_i(t.kind),d=ui(i);if(o>d)throw new It(pi(i,d));let l=this.randomId(),c=this.randomToken(),u={id:l,tokenHash:Pl(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),{...Er(u),token:c}}revoke(t){let n=this.links.get(t);if(!n)throw new on(t);return n.revokedAtMs??=this.now(),Er(n)}getById(t){if(typeof t!="string"||t.length===0)return null;let n=this.links.get(t);return n?Er(n):null}validateResult(t){if(typeof t!="string"||t.length===0)return{status:"invalid"};let n=Pl(t),r=this.now();for(let o of this.links.values())if(Nm(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:Er(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)}};xr();ve();ke();function Ei(e,t){return t}a(Ei,"storeForRequest");function El(e){let t=typeof e.user?.username=="string"?e.user.username:void 0;return t?{kind:"admin",username:t}:{kind:"admin"}}a(El,"adminActor");function rs(e){let t=Ee(e.settings);return new an({defaultTtlSeconds:t.defaultLinkTtlSeconds,maxActiveLinks:t.maxActiveLinks})}a(rs,"createConfiguredLinkStore");function Cl(e){e.setHeader("Cache-Control","no-store"),e.setHeader("Pragma","no-cache")}a(Cl,"noStore");function Il(e,t){return(n,r)=>{let s=Ei(e,t).list();Cl(r),r.json({links:s})}}a(Il,"listLinksHandler");function Ll(e,t,n){return(r,o)=>{let s=r.body??{},i={};if(s.ttlSeconds!==void 0){if(typeof s.ttlSeconds!="number"){o.status(400).json({error:"ttlSeconds must be a positive number",code:"invalid_ttl",hint:Vt});return}i.ttlSeconds=s.ttlSeconds}if(typeof s.label=="string"&&(i.label=s.label),s.kind!==void 0){if(!Pi(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=Ei(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",El(r),{linkId:d.id,label:d.label,expiresAt:d.expiresAt,createdAt:d.createdAt,kind:l,...p!==void 0?{ttlSeconds:p}:{}})}if(Cl(o),l==="get_link"){let c=pa(r,e.settings.httpAdminRoot,d.token);o.status(201).json({...d,url:c});return}o.status(201).json({...d})}catch(d){if(d instanceof rn){o.status(d.statusCode).json({error:d.message,code:d.code});return}if(d instanceof It){o.status(d.statusCode).json({error:d.message,code:d.code,hint:d.hint});return}if(d instanceof _r){o.status(d.statusCode).json({error:d.message,code:d.code});return}B(o,d);return}}}a(Ll,"createLinkHandler");function Nl(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=Ei(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",El(r),{linkId:i.id,label:i.label,expiresAt:i.expiresAt,createdAt:i.createdAt,kind:i.kind,...c!==void 0?{ttlSeconds:c}:{}})}o.json({revoked:!0,link:i})}catch(i){if(i instanceof on){o.status(i.statusCode).json({error:i.message,code:i.code});return}B(o,i);return}}}a(Nl,"revokeLinkHandler");function Fm(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(Fm,"ttlSecondsFromLink");function Om(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=Fm(l);c!==void 0&&(n.ttlSeconds=c)}return{...e,details:n}}a(Om,"enrichAuditLogEntry");function Fl(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=>Om(d,t));r.json({logs:i})}}a(Fl,"listLogsHandler");var Ii=require("@modelcontextprotocol/sdk/server/mcp.js"),Bl=require("@modelcontextprotocol/sdk/server/streamableHttp.js"),P=require("zod");Je();se();$t();ie();tt();qt();zt();Fe();et();var Ol=require("async_hooks");Pe();var $m="ai.redacted",$l=new Ol.AsyncLocalStorage;function Ml(e,t,n,r){if(!(r<=0))try{e.append($m,t,{redactions:r,operation:n})}catch{}}a(Ml,"appendRedactionAudit");function Dl(e){let t=$l.getStore();if(!t)return;let n=wo(e);n>0&&t.record(n)}a(Dl,"noteResponseRedactions");function Hl(e,t,n,r){return $l.run({record:a(o=>Ml(e,t,n,o),"record")},r)}a(Hl,"runWithRedactionAudit");function jl(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(jl,"mcpOperationName");function Ci(e){let t=e.accessLink;if(t)return{kind:"access-link",linkId:t.id,...t.label!==void 0?{label:t.label}:{}}}a(Ci,"accessLinkAuditActor");function Ul(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=Ci(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"}`;Ml(e,i,c,wo(d))}return o(s)}),r()}}a(Ul,"redactionAuditMiddleware");Pe();function ql(){return"0.2.0-beta.7"}a(ql,"pluginVersion");function ae(e){let t=JSON.stringify(e);return Dl(t),{content:[{type:"text",text:t}]}}a(ae,"jsonResult");function Li(e){return e===!0?new Set(["payload","baseFlow"]):new Set}a(Li,"includeSetFromPayloadFlag");function de(e,t=500){let n=Sr(e,"mcp");if(n)return{isError:!0,content:[{type:"text",text:JSON.stringify({...n.body,statusCode:n.statusCode})}]};let r=e instanceof Error?e.message:String(e);return{isError:!0,content:[{type:"text",text:JSON.stringify({error:r,code:"internal_error",statusCode:t})}]}}a(de,"jsonError");var Mm=new Set(["list_proposals","list_my_proposals"]),Dm=new Set(["status","limit","offset","mine","flowId"]);function Hm(e){return!!e&&typeof e=="object"&&typeof e.safeParseAsync=="function"}a(Hm,"isZodLikeSchema");function jm(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(Mm.has(e)&&Dm.has(s))return xn(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 b(d,"invalid_arguments",{details:{parameter:s,errors:i,hint:`Fix '${s}' to match the ${e} input schema (see tools/list).`}})}a(jm,"mcpArgumentError");function zl(e){let t=e,n=t.validateToolInput;typeof n=="function"&&(t.validateToolInput=async(r,o,s)=>{let i=r.inputSchema;if(!Hm(i))return n.call(e,r,o,s);let d=await i.safeParseAsync(o??{});if(d.success)return d.data;let l=Sr(jm(s,o,d.error.issues),"mcp"),c=l?{...l.body,statusCode:l.statusCode}:{};throw new Error(JSON.stringify(c))})}a(zl,"installMcpArgumentErrorMapping");function Wl(e,t,n,r){e.registerTool("get_status",{description:'Return plugin status (version, AI feature flags, storage path, deploy defaults). For access-link callers, permission is null and caller.kind is "access-link" \u2014 that is expected, not an error. Use before other tools to confirm the plugin is enabled.'},()=>{try{return ae(Mn(t,r))}catch(o){return de(o)}}),e.registerTool("list_flows",{description:'List all flows as a flat Admin API v2 array plus the current rev string. Likely secrets are shown as "[REDACTED]" (nodes list redacted paths in _aiRedactions). Returns { rev, flows }. Call this (or get_flow) before submit_proposal so expectedRev matches.'},async()=>{try{return ae(await ut(n.flowAdapter))}catch(o){return de(o,502)}}),e.registerTool("get_flow",{description:"Get one tab/subflow by id with nested nodes/configs and the current rev. Pass summary=true for a lightweight node list (id/type/name/wires/bytes, no field contents). Prefer summary \u2192 search_flows \u2192 get_node_field(lines) \u2192 replace_text \u2192 submit_proposal. Returns { rev, flow } or summary shape. Use rev as expectedRev when submitting.",inputSchema:{id:P.z.string().min(1).describe("Flow / tab id"),summary:P.z.boolean().optional().describe("When true, return node list with sizes only (no field contents)")}},async({id:o,summary:s})=>{try{return ae(s?await Do(n.flowAdapter,o):await Me(n.flowAdapter,o))}catch(i){return de(i,502)}}),e.registerTool("get_node",{description:"Get one full node by id (includes owning flowId when known). Optional flow scopes the lookup.",inputSchema:{id:P.z.string().min(1).describe("Node id"),flow:P.z.string().min(1).optional().describe("Optional tab id to scope the lookup")}},async({id:o,flow:s})=>{try{return ae(await Ho(n.flowAdapter,o,s))}catch(i){return de(i,502)}}),e.registerTool("get_node_field",{description:'Get one string field from a node. Optional lines="A-B" (1-based inclusive) returns a slice; response always includes totalLines and bytes.',inputSchema:{id:P.z.string().min(1).describe("Node id"),field:P.z.string().min(1).describe("Field name (e.g. template, func)"),lines:P.z.string().optional().describe("Optional 1-based line range A or A-B"),flow:P.z.string().min(1).optional().describe("Optional tab id to scope the lookup")}},async({id:o,field:s,lines:i,flow:d})=>{try{return ae(await jo(n.flowAdapter,o,s,{...d?{flowId:d}:{},...i?{lines:i}:{}}))}catch(l){return de(l,502)}}),e.registerTool("search_flows",{description:"Text search across string fields. Returns hits with node id, field, line numbers, and a short snippet. Results are capped; truncated=true when the cap is hit. Optional flow scopes to one tab.",inputSchema:{q:P.z.string().describe("Substring to find (case-sensitive; non-empty required)"),flow:P.z.string().min(1).optional().describe("Optional tab id")}},async({q:o,flow:s})=>{try{return ae(await Uo(n.flowAdapter,o,s?{flowId:s}:{}))}catch(i){return de(i,502)}}),e.registerTool("list_node_types",{description:"List installed Node-RED palette node types (type, module, category, description, \u2026). Optional q filters type/module/category/description (case-insensitive). Results are capped. Call this before proposing nodes with unfamiliar types.",inputSchema:{q:P.z.string().optional().describe("Optional case-insensitive filter")}},async({q:o})=>{try{return ae(await ao(n.nodeTypesAdapter,o?{q:o}:{}))}catch(s){return de(s,502)}}),e.registerTool("get_node_type",{description:"Get detail for one installed node type: defaults (JSON), inputs/outputs (always set), requiredConfigNodeTypes, help sections (summary/inputs/outputs), category/module/color. Types are case-sensitive. Pass includeHelp=false to omit help. 404 node_type_not_found when unknown (didYouMean on case mismatch; else nearest names).",inputSchema:{type:P.z.string().min(1).describe("Node type id (e.g. inject, template) \u2014 case-sensitive"),includeHelp:P.z.boolean().optional().describe("When false, omit help sections (default true)")}},async({type:o,includeHelp:s})=>{try{return ae(await Bt(n.nodeTypesAdapter,o,{includeHelp:s!==!1}))}catch(i){return de(i,502)}}),e.registerTool("search_palette",{description:"Search the public Node-RED catalogue for modules to recommend. Returns module id, description, version, updated, link, and installed boolean (cross-checked against installed modules). Core node-red types are not in the catalogue \u2014 use list_node_types. RECOMMEND ONLY \u2014 never installs.",inputSchema:{q:P.z.string().describe("Search string (id / description / keywords; non-empty required)")}},async({q:o})=>{try{return ae(await lo(n.nodeTypesAdapter,o,{...n.catalogueFetcher?{catalogueFetcher:n.catalogueFetcher}:{},...n.catalogueUrl?{catalogueUrl:n.catalogueUrl}:{}}))}catch(s){return de(s,502)}})}a(Wl,"registerReadTools");function Gl(e,t,n,r,o){let s=V(r,t.settings.httpAdminRoot);e.registerTool("submit_proposal",{description:`Submit a replace_flow proposal for owner approval (does not deploy). Preferred for small string edits: payload: { flowId, nodes: [{ op:"replace_text", node, field, find, replace, occurrence? }] } \u2014 find must be unique unless occurrence (1-based) is set; multiple replace_text ops apply in order; yields a short readable diff. Or send a full tab: payload.flow from get_flow (id, type, label?, nodes[], configs?). Inputs (all top-level, next to each other): type="replace_flow", expectedRev (required string from list_flows/get_flow rev; expected_rev alias accepted), optional title (\u226480 chars), and payload: { flowId, flow? } and/or payload.nodes / top-level nodes. Do not nest expectedRev inside payload. Secret values appear as "[REDACTED]" in reads; leave the placeholder unchanged to keep the stored secret (it is restored on submit), or replace it with a new value to change it. Payload size limit is ${K} bytes (413 if exceeded). Recent known expectedRev snapshots are accepted and auto-rebased; unknown revisions return 409 version_mismatch. Open proposals auto-rebase on revision changes; conflict blocks approval until resolved. Proposals expire after the configured TTL (default 7 days). replace_text errors: 400 text_not_found; 409 ambiguous_match with matchCount. Unknown palette types return 400 unknown_node_type with nearest matches \u2014 call list_node_types / search_palette and ask the user to install via Manage palette (never auto-install). Returns a lean proposal (id, status, title, storedTitle, flowLabel, displayName, reviewUrl, changeSummary, \u2026) by default; title may gain " in <flow>"; storedTitle is the title as saved (your title without that suffix, or the auto-generated title, which may already name the flow). Pass include_payload=true to include payload and baseFlow. When telling the user about the proposal, cite displayName and reviewUrl \u2014 not the raw id. Humans approve on the /ai-access dashboard (reviewUrl opens/scrolls to it).`,inputSchema:{type:P.z.literal("replace_flow").describe("Must be 'replace_flow'"),expectedRev:P.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:P.z.string().min(1).optional().describe("Alias for expectedRev (snake_case); prefer expectedRev"),title:P.z.string().max(80).optional().describe("Optional short human title (\u226480 chars). Auto-generated from changes when omitted."),include_payload:P.z.boolean().optional().describe("When true, include payload and baseFlow in the response (default lean)"),nodes:P.z.array(P.z.record(P.z.string(),P.z.unknown())).optional().describe("Optional node ops (set/add/remove/replace_text). Same as payload.nodes when flow is omitted."),payload:P.z.object({flowId:P.z.string().min(1).describe("Tab id to replace"),flow:P.z.object({id:P.z.string().optional(),type:P.z.string().optional().describe("Usually 'tab' (included by get_flow)"),label:P.z.string().optional(),nodes:P.z.array(P.z.record(P.z.string(),P.z.unknown())).optional().describe("Child nodes for the tab (from get_flow.nodes)"),configs:P.z.array(P.z.record(P.z.string(),P.z.unknown())).optional()}).passthrough().optional().describe("Full tab from get_flow; omit when using nodes ops"),nodes:P.z.array(P.z.record(P.z.string(),P.z.unknown())).optional().describe("Node ops applied to the live tab (prefer replace_text for small string edits)")}).describe("Replacement flow payload and/or node ops")}},async i=>{try{let d=await Tt(n.proposalStore,n.auditLog,o,{...i,expectedRev:typeof i.expectedRev=="string"&&i.expectedRev.length>0?i.expectedRev:i.expected_rev},n.flowAdapter,s,n.nodeTypesAdapter),l={...fe(d,{...s,include:Li(i.include_payload)}),mine:!0},c=ne(n.proposalStore),u=Ye(Ve(n.proposalStore,n.proposalStore.get(d.id),o.id));return ae({...l,...u,...ue(c)})}catch(d){return de(d)}}),Bm(e,n,o,s),qm(e,n,o,s)}a(Gl,"registerProposalTools");function Um(e,t,n,r){let o={mine:P.z.boolean().optional().describe("true: only proposals created by this access link; false: only other links\u2019 proposals"),status:P.z.union([P.z.string(),P.z.array(P.z.string())]).optional().describe("Filter by proposal status: one value, an array, or a comma-separated string (pending, conflict, approved, rejected, failed, withdrawn, expired, superseded). An empty array or empty string means no status filter."),flowId:P.z.string().optional().describe("Filter by flow tab id (payload.flowId)"),limit:P.z.number().int().min(1).max(Sn).optional().describe(`Page size (1\u2013${Sn}). Omit to return all matches.`),offset:P.z.number().int().min(0).optional().describe("Skip this many filtered results (default 0)"),include_payload:P.z.boolean().optional().describe("When true, include payload and baseFlow on each proposal")},s=a(async i=>{try{let d=Li(i.include_payload),l=await Qe(t.flowAdapter),c=l?$e(t.proposalStore,l).expired:ne(t.proposalStore),u={};typeof i.mine=="boolean"&&(u.mine=i.mine);let p=ci(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=Tn(t.proposalStore,n,r,u);return ae({proposals:f.proposals.map(h=>he(t.proposalStore,n,h,{...r,include:d},{includeConflicts:!1})),...Jt({...f,returned:f.proposals.length}),...ue(c)})}catch(d){return de(d)}},"listProposalsHandler");e.registerTool("list_proposals",{description:"List proposals visible to this access link (all proposals by default). Each item includes mine:true|false for the caller. Filter with mine, status, and/or flowId; paginate with limit/offset (response includes total, offset, hasMore, nextOffset). Invalid filters return invalid_filter. Returns lean { proposals, total, \u2026 } with title, displayName, reviewUrl, changeSummary (no payload/baseFlow unless include_payload=true). Prefer displayName + reviewUrl when referring to a proposal with the user (not the raw id).",inputSchema:o},s),e.registerTool("list_my_proposals",{description:"Alias of list_proposals. Prefer list_proposals. Lists proposals visible to this access link with optional mine/status/flowId filters and limit/offset pagination.",inputSchema:o},s)}a(Um,"registerListProposalsTools");function Bm(e,t,n,r){Um(e,t,n,r),e.registerTool("get_proposal",{description:"Get any proposal by id (all tokens can read). Response includes mine:true|false and, for open proposals (pending or conflict), conflictingProposals when other open work touches the same flow. Lean by default (no payload/baseFlow); pass include_payload=true for full bodies. Cite displayName + reviewUrl when talking to the user.",inputSchema:{id:P.z.string().min(1).describe("Proposal id (prop-\u2026)"),include_payload:P.z.boolean().optional().describe("When true, include payload and baseFlow")}},async({id:o,include_payload:s})=>{try{let i=await Qe(t.flowAdapter),d=ct(t.proposalStore,n,o,r);return ae(he(t.proposalStore,n,d,{...r,include:Li(s)},{...i?{live:i}:{}}))}catch(i){return de(i)}})}a(Bm,"registerProposalQueryTools");function qm(e,t,n,r){e.registerTool("get_proposal_diff",{description:"Diff any proposal against its stored baseFlow snapshot (what changed). Optional format=summary|full (default summary). Open proposals include rebase and liveChanges; conflict blocks approval, otherwise vsCurrent shows the merged deployment.",inputSchema:{id:P.z.string().min(1).describe("Proposal id (prop-\u2026)"),format:P.z.enum(["summary","full"]).optional().describe("Diff payload format (default summary)")}},async({id:o,format:s})=>{try{let i=ct(t.proposalStore,n,o),d=In(s??"summary");if(!d.ok)return de(new b("Invalid diff format","invalid_diff_format",{details:{hint:"Valid formats: summary, full (default summary)."}}));let l=Ln(await $n(i,t.flowAdapter,t.proposalStore.get(o)),d.format);return ae(l)}catch(i){return de(i)}}),e.registerTool("resolve_proposal_conflict",{description:"Replace your own open proposal with a resolution based on expectedRev. Revision changes auto-rebase; conflict blocks approval until resolved. Open proposals expire after the configured TTL (default 7 days). Read the current flow first; payload accepts a full flow or nodes ops as in submit_proposal.",inputSchema:{id:P.z.string().min(1),expectedRev:P.z.string().min(1),title:P.z.string().max(80).optional(),payload:P.z.object({flowId:P.z.string().min(1),flow:P.z.record(P.z.string(),P.z.unknown()).optional(),nodes:P.z.array(P.z.record(P.z.string(),P.z.unknown())).optional()})}},async({id:o,...s})=>{try{let i=await To(t.proposalStore,t.auditLog,n,o,s,t.flowAdapter,r,t.nodeTypesAdapter);return ae(he(t.proposalStore,n,i,r))}catch(i){return de(i)}}),e.registerTool("withdraw_proposal",{description:"Withdraw one of YOUR open (pending or conflict) proposals (status becomes withdrawn, not rejected). Optional reason. Only the creating access link can withdraw; other tokens get 403. Cite displayName + reviewUrl when telling the user.",inputSchema:{id:P.z.string().min(1).describe("Proposal id (prop-\u2026) owned by this access link"),reason:P.z.string().optional().describe("Optional withdraw reason")}},({id:o,reason:s})=>{try{let i=Rn(t.proposalStore,t.auditLog,n,o,s,r);return ae(he(t.proposalStore,n,i,r))}catch(i){return de(i)}})}a(qm,"registerProposalActionTools");function zm(e,t,n){let r=new Ii.McpServer({name:"node-red-ai-access",version:ql()});zl(r);let o=n.accessLink;if(!o)throw new Error("Access link required");return Wl(r,e,t,n),Gl(r,e,t,n,o),r}a(zm,"createAiMcpServer");function Wm(e){let t=Ci(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:to(n,r),label:"oauth"}}a(Wm,"mcpAuditActor");function Ni(e,t){return async(n,r,o)=>{try{if(!n.accessLink&&!n.auth){r.setHeader("WWW-Authenticate",ce),r.status(401).json({error:xe,code:qe});return}let s={...t,flowAdapter:kt(t.flowAdapter)},i=n.accessLink?zm(e,s,n):Gm(e,s,n),d=new Bl.StreamableHTTPServerTransport({enableJsonResponse:!0});await i.connect(d),await Hl(t.auditLog,Wm(n),jl(n.body),()=>d.handleRequest(n,r,n.body)),r.on("close",()=>{d.close(),i.close()})}catch(s){if(!r.headersSent){B(r,s);return}o(s instanceof Error?s:new Error(String(s)))}}}a(Ni,"mcpRpcPostHandler");function Gm(e,t,n){let r=new Ii.McpServer({name:"node-red-ai-access",version:ql()});if(zl(r),Wl(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:to(i,s),kind:"mcp",createdAt:new Date().toISOString(),expiresAt:new Date((n.auth?.expiresAt??0)*1e3).toISOString(),createdBy:s,oauthClientId:i,oauthUser:s};Gl(r,e,t,n,l)}return r}a(Gm,"createOAuthMcpServer");function Cr(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(Cr,"mcpRpcMethodNotAllowed");var Ir=require("crypto"),Hn=require("fs"),Mi=require("path"),Q=require("@modelcontextprotocol/sdk/server/auth/errors.js"),Xl=require("@modelcontextprotocol/sdk/server/auth/handlers/authorize.js"),Di=require("@modelcontextprotocol/sdk/server/auth/handlers/metadata.js"),Zl=require("@modelcontextprotocol/sdk/server/auth/handlers/register.js"),Ql=require("@modelcontextprotocol/sdk/server/auth/handlers/revoke.js"),ec=require("@modelcontextprotocol/sdk/server/auth/handlers/token.js"),tc=require("@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js"),nc=require("@modelcontextprotocol/sdk/server/auth/router.js"),rc=zn(require("express"),1);se();Be();function rt(e){return e.replace(/[&<>"']/g,t=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[t]??t)}a(rt,"esc");var Kl={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>'},Km={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."]},Fi='<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>',Jm=`
|
|
988
991
|
:root{color-scheme:light;--bg:#f3f4f6;--card:#fff;--text:#111827;--muted:#4b5563;--line:#e5e7eb;--soft:#f9fafb;--brand:#8f0000;--brand-2:#b91c1c;--ok:#0f766e;--warn-bg:#fff7ed;--warn:#9a3412;--focus:#2563eb}
|
|
989
992
|
*{box-sizing:border-box}
|
|
990
993
|
body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px 16px;background:var(--bg);color:var(--text);font:15px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
|
|
@@ -1020,7 +1023,34 @@ form.login input{font:inherit;padding:9px 11px;border-radius:10px;border:1px sol
|
|
|
1020
1023
|
a{color:var(--focus)}
|
|
1021
1024
|
[hidden]{display:none!important}
|
|
1022
1025
|
@media (max-width:420px){.actions{flex-direction:column-reverse}.secondary{flex:1}}
|
|
1023
|
-
`;function
|
|
1026
|
+
`;function Jl(e){let t=e.scopes.map(n=>{let[r,o]=Km[n]??[n,""];return`<li data-scope="${rt(n)}">${Kl[n]??Kl.read}<div><b>${rt(r)}</b><span>${rt(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>${Jm}</style></head><body><main class="card" data-request="${rt(e.requestId)}" data-token-key="${rt(e.tokenKey)}" data-admin-root="${rt(e.adminRoot)}"><div class="head">${Fi}<div class="brand"><b>Node-RED AI Access</b><span>Authorization request</span></div></div><div class="body"><h1><span class="app">${rt(e.redirectHost)}</span> wants to access your Node-RED</h1><p class="sub">Access tokens will be sent to <span class="host">${rt(e.redirectHost)}</span>. App name (provided by the client, unverified): <b>${rt(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="${rt(e.nonce)}">${e.script}</script></body></html>`}a(Jl,"renderConsentPage");var os=`
|
|
1027
|
+
function nrLoginType(adminRootHref) {
|
|
1028
|
+
return fetch(new URL('auth/login', adminRootHref).href, {
|
|
1029
|
+
headers: { 'Node-RED-API-Version': 'v2' }, credentials: 'same-origin'
|
|
1030
|
+
})
|
|
1031
|
+
.then(function (r) { return r.json(); })
|
|
1032
|
+
.then(function (cfg) { return cfg && typeof cfg.type === 'string' ? cfg.type : null; })
|
|
1033
|
+
.catch(function () { return null; });
|
|
1034
|
+
}
|
|
1035
|
+
function nrPasswordSignIn(adminRootHref, tokenKey, username, password) {
|
|
1036
|
+
var body = new URLSearchParams({
|
|
1037
|
+
client_id: 'node-red-editor', grant_type: 'password', scope: '*',
|
|
1038
|
+
username: username, password: password
|
|
1039
|
+
});
|
|
1040
|
+
return fetch(new URL('auth/token', adminRootHref).href, {
|
|
1041
|
+
method: 'POST', credentials: 'same-origin',
|
|
1042
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Node-RED-API-Version': 'v2' },
|
|
1043
|
+
body: body.toString()
|
|
1044
|
+
})
|
|
1045
|
+
.then(function (r) { return r.json().catch(function () { return {}; }).then(function (b) { return { r: r, b: b }; }); })
|
|
1046
|
+
.then(function (x) {
|
|
1047
|
+
if (!x.r.ok || !x.b.access_token) return { ok: false };
|
|
1048
|
+
try { localStorage.setItem(tokenKey, JSON.stringify(x.b)); } catch (e) {}
|
|
1049
|
+
return { ok: true };
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
`;var Vl=`(function () {
|
|
1053
|
+
${os}
|
|
1024
1054
|
var main = document.querySelector('main');
|
|
1025
1055
|
var id = main.getAttribute('data-request');
|
|
1026
1056
|
var key = main.getAttribute('data-token-key');
|
|
@@ -1054,10 +1084,9 @@ a{color:var(--focus)}
|
|
|
1054
1084
|
}
|
|
1055
1085
|
function showSignIn() {
|
|
1056
1086
|
show(allow, false); show(deny, false); show(user, false); show(grant, false);
|
|
1057
|
-
|
|
1058
|
-
.then(function (
|
|
1059
|
-
|
|
1060
|
-
if (cfg && cfg.type === 'credentials') {
|
|
1087
|
+
nrLoginType(adminRoot.href)
|
|
1088
|
+
.then(function (type) {
|
|
1089
|
+
if (type === 'credentials') {
|
|
1061
1090
|
setStatus('Sign in with your Node-RED account to continue.');
|
|
1062
1091
|
show(login, true);
|
|
1063
1092
|
$('username').focus();
|
|
@@ -1065,10 +1094,6 @@ a{color:var(--focus)}
|
|
|
1065
1094
|
editorLink('Sign in to the ', ' in this browser, then press Retry.');
|
|
1066
1095
|
show(retry, true);
|
|
1067
1096
|
}
|
|
1068
|
-
})
|
|
1069
|
-
.catch(function () {
|
|
1070
|
-
editorLink('Sign in to the ', ' in this browser, then press Retry.');
|
|
1071
|
-
show(retry, true);
|
|
1072
1097
|
});
|
|
1073
1098
|
}
|
|
1074
1099
|
function load() {
|
|
@@ -1100,21 +1125,11 @@ a{color:var(--focus)}
|
|
|
1100
1125
|
var btn = $('login-submit');
|
|
1101
1126
|
btn.disabled = true;
|
|
1102
1127
|
setStatus('Signing in\u2026');
|
|
1103
|
-
|
|
1104
|
-
client_id: 'node-red-editor', grant_type: 'password', scope: '*',
|
|
1105
|
-
username: $('username').value, password: $('password').value
|
|
1106
|
-
});
|
|
1107
|
-
fetch(new URL('auth/token', adminRoot).href, {
|
|
1108
|
-
method: 'POST', credentials: 'same-origin',
|
|
1109
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Node-RED-API-Version': 'v2' },
|
|
1110
|
-
body: body.toString()
|
|
1111
|
-
})
|
|
1112
|
-
.then(function (r) { return r.json().catch(function () { return {}; }).then(function (b) { return { r: r, b: b }; }); })
|
|
1128
|
+
nrPasswordSignIn(adminRoot.href, key, $('username').value, $('password').value)
|
|
1113
1129
|
.then(function (x) {
|
|
1114
1130
|
btn.disabled = false;
|
|
1115
1131
|
$('password').value = '';
|
|
1116
|
-
if (!x.
|
|
1117
|
-
try { localStorage.setItem(key, JSON.stringify(x.b)); } catch (e) {}
|
|
1132
|
+
if (!x.ok) { setStatus('Sign in failed. Check your username and password.', true); return; }
|
|
1118
1133
|
load();
|
|
1119
1134
|
})
|
|
1120
1135
|
.catch(function (e) { btn.disabled = false; setStatus('Error: ' + e.message, true); });
|
|
@@ -1159,14 +1174,14 @@ a{color:var(--focus)}
|
|
|
1159
1174
|
document.addEventListener('visibilitychange', function () { if (document.visibilityState === 'visible') autoRetry(false); });
|
|
1160
1175
|
window.addEventListener('focus', function () { autoRetry(false); });
|
|
1161
1176
|
load();
|
|
1162
|
-
})();`;ue();var dt=a(e=>(0,Yn.createHash)("sha256").update(e).digest("hex"),"hash"),It=a(()=>(0,Yn.randomBytes)(32).toString("base64url"),"secret"),hm=36e5,gm=25,ym=5,wm=500,xd=a(()=>({clients:[],codes:[],tokens:[]}),"emptyDb"),po=class{constructor(t,n){this.maxClients=n;this.path=(0,Ps.join)(t.root,"oauth.json");try{let r=(0,mn.existsSync)(this.path)?JSON.parse((0,mn.readFileSync)(this.path,"utf8")):xd();if(!r||typeof r!="object"||!("clients"in r)||!("codes"in r)||!("tokens"in r)||!Array.isArray(r.clients)||!Array.isArray(r.codes)||!Array.isArray(r.tokens))throw new Error("Invalid OAuth store");this.db=r}catch{this.db=xd()}(0,mn.mkdirSync)(t.root,{recursive:!0,mode:448}),this.prune()}static{a(this,"OAuthStore")}db;path;save(){Ot((0,Ps.join)(this.path,".."),this.path,Je(this.db,8e6,{kind:"oauth",pretty:!0}))}prune(t=Date.now()){let n=this.db.clients.length+this.db.codes.length+this.db.tokens.length,r=new Set(this.db.clients.filter(o=>o.approved?(o.lastUsedAt??(o.client_id_issued_at??0)*1e3)>t-30*864e5:(o.client_id_issued_at??0)*1e3>t-hm).map(o=>o.client_id));this.db.clients=this.db.clients.filter(o=>r.has(o.client_id)),this.db.codes=this.db.codes.filter(o=>o.expires>t&&r.has(o.clientId)),this.db.tokens=this.db.tokens.filter(o=>r.has(o.clientId)&&o.expires>t),this.db.clients.length+this.db.codes.length+this.db.tokens.length!==n&&this.save()}getClient(t){return this.prune(),this.db.clients.find(n=>n.client_id===t)}registerClient(t){if(this.prune(),this.approvedCount()>=this.maxClients())throw new B.InvalidClientMetadataError("Maximum active OAuth clients reached");if(this.db.clients.length-this.approvedCount()>=gm)throw new B.InvalidClientMetadataError("Too many OAuth clients awaiting consent");if(!Array.isArray(t.redirect_uris)||!t.redirect_uris.length||t.redirect_uris.some(r=>{try{let o=new URL(r);return!!o.username||!!o.password||!!o.hash||o.protocol!=="https:"&&!(o.protocol==="http:"&&["localhost","127.0.0.1","[::1]"].includes(o.hostname))}catch{return!0}}))throw new B.InvalidClientMetadataError("Redirect URIs must use HTTPS or loopback HTTP");if(t.token_endpoint_auth_method&&t.token_endpoint_auth_method!=="none")throw new B.InvalidClientMetadataError("Only public clients are supported");let n={...t,client_secret:void 0,client_secret_expires_at:void 0,client_id:It(),client_id_issued_at:Math.floor(Date.now()/1e3),token_endpoint_auth_method:"none",lastUsedAt:Date.now()};return this.db.clients.push(n),this.save(),n}approvedCount(){return this.db.clients.filter(t=>t.approved).length}approve(t){let n=this.db.clients.find(r=>r.client_id===t);if(!(!n||n.approved)){if(this.approvedCount()>=this.maxClients())throw new B.InvalidClientMetadataError("Maximum active OAuth clients reached");n.approved=!0,this.save()}}get data(){return this.db}persist(){this.touchTimer&&(clearTimeout(this.touchTimer),this.touchTimer=void 0),this.save()}touchTimer;touch(){this.touchTimer||(this.touchTimer=setTimeout(()=>{this.touchTimer=void 0,this.save()},5e3),this.touchTimer.unref?.())}revokeClient(t){let n=this.db.clients.length;return this.db.clients=this.db.clients.filter(r=>r.client_id!==t),this.db.tokens=this.db.tokens.filter(r=>r.clientId!==t),this.db.codes=this.db.codes.filter(r=>r.clientId!==t),this.save(),this.db.clients.length!==n}};function Ls(e){switch(e.source){case"http-in":return`Route ${e.path} is already used by http-in node '${e.nodeName??e.nodeId??"unnamed"}'`;case"httpStatic":return`File ${e.path} is served by httpStatic`;case"middleware":return`Path prefix is already handled by mounted middleware (${e.layer??"unknown"})`;default:return`Route ${e.path} is already registered by another handler (${e.layer??"route"})`}}a(Ls,"describeOAuthRouteCollision");var Is=a((e,t)=>e.toLowerCase().startsWith(t.toLowerCase()),"startsWithCi");function Rs(e){return e===!1?void 0:`/${typeof e=="string"?e:"/"}`.replace(/\/+/g,"/").replace(/\/$/,"")}a(Rs,"normalizeRoot");function _s(e,t){if(e!==void 0){if(!e)return t;if(Is(t,`${e}/`))return t.slice(e.length)}}a(_s,"relativePrefix");function bm(e){let t=new Map;for(let r of e)r.id&&t.set(r.id,r);let n=a((r,o=new Set)=>{if(!r)return!0;let s=t.get(r);return s?s.type==="tab"?!s.disabled&&!s.d:s.type!=="subflow"||o.has(r)?!1:(o.add(r),e.some(i=>i.type===`subflow:${r}`&&!i.d&&n(i.z,o))):!0},"live");return n}a(bm,"makeLiveCheck");function vm(e,t){if(!e||!t.length)return[];let n=[];try{e(s=>n.push(s))}catch{return[]}let r=bm(n),o=[];for(let s of n){if(s.type!=="http in"||typeof s.url!="string"||s.d)continue;let i=s.url.startsWith("/")?s.url:`/${s.url}`;!t.some(d=>Is(i,d))||!r(s.z)||o.push({path:i,source:"http-in",nodeId:s.id,nodeName:s.name?s.name:void 0,flowId:s.z})}return o}a(vm,"findHttpInCollisions");var km=new Set(["bound dispatch","handle"]),Am=["authorize","token","register","revoke"],Sm=["authorization-server","protected-resource"];function xm(e){let t=e.length;for(;t>0&&e.charCodeAt(t-1)===47;)t--;return e.slice(0,t)}a(xm,"trimTrailingSlashes");function Tm(e){let t=new Set;return e.filter(n=>{let r=`${n.source}\0${n.path.toLowerCase()}`;return t.has(r)?!1:(t.add(r),!0)})}a(Tm,"dedupe");function Id(e){let t=Rs(e.httpAdminRoot),n=e.httpNode?Rs(e.httpNodeRoot):void 0,r=`${t??""}/mcp/oauth/`,o=e.metadataFallback?`${t??""}/mcp/.well-known/oauth-`:`${n??""}/.well-known/oauth-`,s=a(p=>{let f=_s(t,p),m=_s(n,p),h=m?vm(e.eachNode,[m]):[],g=new Set(h.map(C=>C.path.toLowerCase())),k=m?Cs(e.httpNode,[m]).filter(C=>!(C.source==="route"&&km.has(C.layer??"")&&g.has(C.path.toLowerCase()))):[],T=f?Cs(e.httpAdmin,[f]):[];return Tm([...T,...k,...h])},"scan"),i=s(r),d=s(o),l=e.fileExists??(()=>!1),c=(e.staticDirs??[]).map(p=>typeof p=="string"?{path:p,root:e.httpStaticRoot}:p),u=a((p,f)=>{for(let m of c){let h=Rs(m.root),g=xm(m.path);for(let k of p){let T=_s(h,k);!T||!l(`${g}${T}`)||f.push({path:k,source:"httpStatic",file:`${g}${T}`})}}},"checkStatic");return u(Am.map(p=>`${r}${p}`),i),u(Sm.map(p=>`${o}${p}`),d),{authorization:i,metadata:d}}a(Id,"scanOAuthRouteCollisions");function Rm(e){let t=a(n=>{try{return e?.[n]}catch{return}},"read");return t("_router")?.stack??t("router")?.stack??[]}a(Rm,"routerStack");function _m(e){let t=e.route?.path;return t?(Array.isArray(t)?t:[t]).map(String):[]}a(_m,"layerPaths");function Pm(e,t){if(e.name==="router")return!1;let n=e.regexp,r=e.matchers??(n?[s=>n.test(s)]:[]);if(!r.length)return!1;let o=a(s=>r.some(i=>!!i(s)),"matches");return o("/unrelated-ai-access-collision-probe")?!1:t.some(s=>o(`${s}token`)||o(`${s}mcp`))}a(Pm,"middlewareHits");function Cs(e,t=["/.well-known/oauth-","/mcp/oauth/"]){let n=[];for(let r of Rm(e)){if(r.handle?.oauthOwned)continue;let o=_m(r);for(let s of o)t.some(i=>Is(s,i))&&n.push({path:s,source:"route",layer:r.name??"route"});!o.length&&Pm(r,t)&&n.push({path:t.join(", "),source:"middleware",layer:r.name??"route"}),r.handle?.stack&&n.push(...Cs({router:{stack:r.handle.stack}},t))}return n}a(Cs,"detectOAuthRouteCollisionDetails");var fo=class{constructor(t,n,r,o,s=()=>0){this.clientsStore=t;this.resource=n;this.accessTtl=r;this.refreshTtl=o;this.refreshMax=s}static{a(this,"LocalOAuthProvider")}skipLocalPkceValidation=!1;inflight=new Map;async authorize(t,n,r){throw new B.InvalidGrantError("Admin consent is required")}async challengeForAuthorizationCode(t,n){let r=this.findCode(t,n);return this.clientsStore.data.codes=this.clientsStore.data.codes.filter(o=>o!==r),this.clientsStore.persist(),this.inflight.set(r.hash,r),setTimeout(()=>this.inflight.delete(r.hash),3e4).unref?.(),r.challenge}async exchangeAuthorizationCode(t,n,r,o,s){let i=this.inflight.get(dt(n))??this.findCode(t,n);if(this.inflight.delete(i.hash),this.clientsStore.data.codes=this.clientsStore.data.codes.filter(d=>d!==i),this.clientsStore.persist(),i.clientId!==t.client_id||i.expires<=Date.now()||o!==i.redirectUri||s&<(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&<(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&<(t.href)!==n)throw new B.InvalidGrantError("Resource mismatch");return n}findCode(t,n){let r=this.clientsStore.data.codes.find(o=>o.hash===dt(n)&&o.clientId===t.client_id);if(!r||r.expires<=Date.now())throw new B.InvalidGrantError("Invalid or expired authorization code");return r}issue(t,n,r,o,s=It(),i=Date.now()){let d=It(),l=It(),c=Date.now(),u=this.refreshMax()*1e3,p=Math.min(c+this.refreshTtl()*1e3,u>0?i+u:1/0),f=this.clientsStore.data,m=f.clients.find(h=>h.client_id===t);return m&&(m.lastUsedAt=c),f.tokens.push({hash:dt(d),type:"access",clientId:t,family:s,scopes:n,resource:r,expires:c+this.accessTtl()*1e3,user:o,rotated:!1},{hash:dt(l),type:"refresh",clientId:t,family:s,scopes:n,resource:r,expires:p,user:o,rotated:!1,familyStart:i}),this.clientsStore.persist(),{access_token:d,token_type:"Bearer",expires_in:this.accessTtl(),refresh_token:l,scope:n.join(" ")}}};function lt(e){let t=new URL(e);if(!["http:","https:"].includes(t.protocol)||t.username||t.password||t.search||t.hash)throw new Error("Invalid resource URL");return`${t.protocol}//${t.host}${t.pathname.replace(/\/+$/,"")}`}a(lt,"canonicalResource");var fn=["read","propose"];function Em(e){let t=(e??"/").split("/").filter(Boolean).join("/");return t?`/${t}/`:"/"}a(Em,"consentAdminRoot");var Cm=new Set(["localhost","127.0.0.1","[::1]"]);function Lt(e){return typeof e=="string"?e:""}a(Lt,"value");function Nd(e,t,n,r){let o=a(S=>`${n().replace(/\/$/,"")}${S}`,"abs"),s=a((S,b,w,v)=>{let A=b.url;b.url="/",S(b,w,E=>{b.url=A,v(E)})},"atRoot"),i=new Map,d=a(()=>{let S=Date.now();for(let[b,w]of i)w.expires<=S&&i.delete(b)},"sweep"),l=a(S=>{d();let b=[...i].filter(([,w])=>w.clientId===S);for(;b.length>=ym;)i.delete(b.shift()[0]);return i.size<wm},"reservePending"),c=a((S,b)=>{S.setHeader("Cache-Control","no-store"),S.setHeader("X-Frame-Options","DENY"),S.setHeader("Referrer-Policy","no-referrer"),S.setHeader("X-Content-Type-Options","nosniff"),S.setHeader("Content-Security-Policy",`default-src 'none'; ${b?`script-src 'nonce-${b}'; `:""}style-src 'unsafe-inline'; connect-src 'self'; form-action 'none'; base-uri 'none'; frame-ancestors 'none'`)},"securityHeaders"),u=mr(e.settings.httpAdminRoot),p=a(S=>{if(!z(e.settings))return[...fn];let b=Ye(S);return b==="*"?[...fn]:b==="read"?["read"]:[]},"grantableFor"),f=a(S=>{let b=S.get("sec-fetch-site");if(b&&b!=="same-origin")return!1;let w=S.get("origin");if(!w)return!0;let v=new Set;try{v.add(new URL(n()).origin)}catch{}let A=S.get("host");return A&&(v.add(`http://${A}`),v.add(`https://${A}`)),v.has(w)},"sameOrigin"),m=a((S,b)=>{d();let w=S.query,v=t.clientsStore.getClient(Lt(w.client_id));if(!v){b.status(400).send("Unknown OAuth client");return}let A=Lt(w.redirect_uri)||(v.redirect_uris.length===1?v.redirect_uris[0]:"");if(!v.redirect_uris.some(vo=>(0,Td.redirectUriMatches)(A,vo))){b.status(400).send("Unregistered redirect URI");return}if(w.response_type!=="code"||w.code_challenge_method!=="S256"||!/^[A-Za-z0-9_-]{43,128}$/.test(Lt(w.code_challenge))){b.status(400).send("Authorization code with PKCE S256 is required");return}let E=[...new Set((Lt(w.scope)||"read").split(" ").filter(Boolean))];if(E.some(vo=>!fn.includes(vo))){b.status(400).send("Unsupported scope");return}let $=Lt(w.resource)||r(),F;try{F=lt($)}catch{b.status(400).send("Invalid resource");return}if(F!==lt(r())){b.status(400).send("Resource mismatch");return}if(!l(v.client_id)){b.status(429).send("Too many pending authorization requests; try again later");return}let le=It();i.set(le,{clientId:v.client_id,redirect:A,challenge:Lt(w.code_challenge),scopes:E,resource:$,state:Lt(w.state),csrf:It(),expires:Date.now()+10*6e4});let Ke=new URL(A).host,Ft=(0,Yn.randomBytes)(16).toString("base64");c(b,Ft),b.status(200).type("html").send(Ad({requestId:le,tokenKey:u,adminRoot:Em(e.settings.httpAdminRoot),nonce:Ft,redirectHost:Ke,loopbackRedirect:Cm.has(new URL(A).hostname),clientName:v.client_name||"Unnamed client",scopes:E,script:Sd}))},"authorizePage"),h=a((S,b,w)=>{c(b),Ld.default.json({limit:"4kb"})(S,b,()=>Ae(e,"api")(S,b,()=>{try{d();let v=/^\/mcp\/oauth\/consent\/([A-Za-z0-9_-]+)\/?$/.exec(S.path)?.[1]??"",A=i.get(v),E=A&&t.clientsStore.getClient(A.clientId);if(!A||!E){b.status(404).json({error:"Consent request expired or unknown"});return}let $=z(e.settings)&&S.user?.username||"owner",F=A.scopes.filter(Ft=>p(S).includes(Ft));if(!F.length){b.status(403).json({error:"Your Node-RED permissions do not allow granting the requested scopes",grantable:F});return}if(S.method==="GET"){A.approver=$,b.json({client_name:E.client_name??null,redirect_host:new URL(A.redirect).host,requested:A.scopes,grantable:F,user:$,csrf:A.csrf});return}if(S.method!=="POST"){b.status(405).end();return}let le=S.body??{};if(!f(S)||le.csrf!==A.csrf||A.approver!==$){b.status(403).json({error:"Consent request verification failed"});return}i.delete(v);let Ke=new URL(A.redirect);if(le.decision!=="allow")Ke.searchParams.set("error","access_denied");else{try{t.clientsStore.approve(E.client_id)}catch{b.status(409).json({error:"Maximum active OAuth clients reached"});return}let Ft=t.createCode(E,{redirectUri:A.redirect,codeChallenge:A.challenge,scopes:F,resource:new URL(A.resource)},$);Ke.searchParams.set("code",Ft)}A.state&&Ke.searchParams.set("state",A.state),b.json({redirect:Ke.href})}catch(v){w(v)}}))},"consentApi"),g=(0,Rd.clientRegistrationHandler)({clientsStore:t.clientsStore,rateLimit:{windowMs:36e5,max:10,standardHeaders:!0,legacyHeaders:!1}}),k=(0,Pd.tokenHandler)({provider:t}),T=(0,_d.revocationHandler)({provider:t}),C,L="";return{server:a((S,b,w)=>{let v=S.path,A=v.replace(/\/+$/,"").toLowerCase();if(A==="/mcp/oauth/authorize"){S.method==="GET"?m(S,b,w):b.status(405).set("Allow","GET").end();return}if(A.startsWith("/mcp/oauth/consent/")){h(S,b,w);return}if(!["/mcp/oauth/token","/mcp/oauth/register","/mcp/oauth/revoke"].includes(A)){w();return}let E=n();if(E.startsWith("https://")||/^http:\/\/(localhost|127\.0\.0\.1)(:|\/|$)/.test(E)){if(L!==E)try{C=(0,Cd.mcpAuthRouter)({provider:t,issuerUrl:new URL(E),baseUrl:new URL(`${E.replace(/\/$/,"")}/mcp/oauth/`),resourceServerUrl:new URL(r()),scopesSupported:fn,clientRegistrationOptions:{rateLimit:{windowMs:36e5,max:10}}}),L=E}catch{C=void 0}if(C){let $=S.url;S.url=S.url.slice(10)||"/",C(S,b,F=>{S.url=$,w(F)});return}}if(v==="/mcp/oauth/token"){s(k,S,b,w);return}if(v==="/mcp/oauth/register"){s(g,S,b,w);return}if(v==="/mcp/oauth/revoke"){s(T,S,b,w);return}w()},"server"),metadata:a((S,b,w)=>{let v=S.path;if(v==="/.well-known/oauth-authorization-server"){s((0,Es.metadataHandler)({issuer:n(),authorization_endpoint:o("/mcp/oauth/authorize"),token_endpoint:o("/mcp/oauth/token"),registration_endpoint:o("/mcp/oauth/register"),revocation_endpoint:o("/mcp/oauth/revoke"),response_types_supported:["code"],code_challenge_methods_supported:["S256"],grant_types_supported:["authorization_code","refresh_token"],token_endpoint_auth_methods_supported:["none"],scopes_supported:fn}),S,b,w);return}if(v==="/.well-known/oauth-protected-resource/mcp"||v==="/.well-known/oauth-protected-resource"){s((0,Es.metadataHandler)({resource:lt(r()),authorization_servers:[n()],scopes_supported:fn}),S,b,w);return}w()},"metadata"),auth:a((S,b,w)=>{try{let v=b.set.bind(b);b.set=((A,E)=>(A==="WWW-Authenticate"&&typeof E=="string"&&(E=E.replace('scope="read"','scope="read propose"')),typeof A=="string"?v(A,E??""):v(A))),(0,Ed.requireBearerAuth)({verifier:t,requiredScopes:["read"],resourceMetadataUrl:o(e.settings.httpNodeRoot===!1||!e.httpNode?"/mcp/.well-known/oauth-protected-resource/mcp":"/.well-known/oauth-protected-resource/mcp"),expectedResource:new URL(r())})(S,b,A=>{b.set=v,w(A)})}catch(v){w(v)}},"auth")}}a(Nd,"makeOAuthHandlers");var Xn=require("fs"),hn=require("path"),Fd=require("url"),Od=require("yaml"),Fm={};function Lm(){return typeof __dirname<"u"?__dirname:(0,hn.dirname)((0,Fd.fileURLToPath)(Fm.url))}a(Lm,"moduleDir");function $d(){let e=Lm(),t=[(0,hn.join)(e,"..","docs","api","openapi.yaml"),(0,hn.join)(e,"..","..","docs","api","openapi.yaml"),(0,hn.join)(process.cwd(),"docs","api","openapi.yaml")];for(let n of t)if((0,Xn.existsSync)(n))return n;return null}a($d,"resolveOpenApiYamlPath");function Im(){let e=$d();if(!e)throw new Error("OpenAPI specification file was not found");return(0,Xn.readFileSync)(e,"utf8")}a(Im,"loadOpenApiYaml");function Md(){return(e,t)=>{try{let n=Im();t.status(200).type("application/yaml").send(n)}catch(n){let r=n instanceof Error?n.message:String(n);t.status(500).json({error:r})}}}a(Md,"openApiYamlHandler");var Ns;function Nm(){let e=$d();if(!e)throw new Error("OpenAPI specification file was not found");return Ns?.path!==e&&(Ns={path:e,json:JSON.stringify((0,Od.parse)((0,Xn.readFileSync)(e,"utf8")))}),Ns.json}a(Nm,"loadOpenApiJson");function Dd(){return(e,t)=>{try{t.status(200).type("application/json").send(Nm())}catch(n){let r=n instanceof Error?n.message:String(n);t.status(500).json({error:r})}}}a(Dd,"openApiJsonHandler");function Om(){return`
|
|
1177
|
+
})();`;ke();var Lt=a(e=>(0,Ir.createHash)("sha256").update(e).digest("hex"),"hash"),ln=a(()=>(0,Ir.randomBytes)(32).toString("base64url"),"secret"),Vm=36e5,Ym=25,Xm=5,Zm=500,Yl=a(()=>({clients:[],codes:[],tokens:[]}),"emptyDb"),ss=class{constructor(t,n){this.maxClients=n;this.path=(0,Mi.join)(t.root,"oauth.json");try{let r=(0,Hn.existsSync)(this.path)?JSON.parse((0,Hn.readFileSync)(this.path,"utf8")):Yl();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=Yl()}(0,Hn.mkdirSync)(t.root,{recursive:!0,mode:448}),this.prune()}static{a(this,"OAuthStore")}db;path;save(){cn((0,Mi.join)(this.path,".."),this.path,ft(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-Vm).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 Q.InvalidClientMetadataError("Maximum active OAuth clients reached");if(this.db.clients.length-this.approvedCount()>=Ym)throw new Q.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 Q.InvalidClientMetadataError("Redirect URIs must use HTTPS or loopback HTTP");if(t.token_endpoint_auth_method&&t.token_endpoint_auth_method!=="none")throw new Q.InvalidClientMetadataError("Only public clients are supported");let n={...t,client_secret:void 0,client_secret_expires_at:void 0,client_id:ln(),client_id_issued_at:Math.floor(Date.now()/1e3),token_endpoint_auth_method:"none",lastUsedAt:Date.now()};return this.db.clients.push(n),this.save(),n}approvedCount(){return this.db.clients.filter(t=>t.approved).length}approve(t){let n=this.db.clients.find(r=>r.client_id===t);if(!(!n||n.approved)){if(this.approvedCount()>=this.maxClients())throw new Q.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 ji(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(ji,"describeOAuthRouteCollision");var Ui=a((e,t)=>e.toLowerCase().startsWith(t.toLowerCase()),"startsWithCi");function Oi(e){return e===!1?void 0:`/${typeof e=="string"?e:"/"}`.replace(/\/+/g,"/").replace(/\/$/,"")}a(Oi,"normalizeRoot");function $i(e,t){if(e!==void 0){if(!e)return t;if(Ui(t,`${e}/`))return t.slice(e.length)}}a($i,"relativePrefix");function Qm(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(Qm,"makeLiveCheck");function eg(e,t){if(!e||!t.length)return[];let n=[];try{e(s=>n.push(s))}catch{return[]}let r=Qm(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=>Ui(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(eg,"findHttpInCollisions");var tg=new Set(["bound dispatch","handle"]),ng=["authorize","token","register","revoke"],rg=["authorization-server","protected-resource"];function og(e){let t=e.length;for(;t>0&&e.charCodeAt(t-1)===47;)t--;return e.slice(0,t)}a(og,"trimTrailingSlashes");function sg(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(sg,"dedupe");function oc(e){let t=Oi(e.httpAdminRoot),n=e.httpNode?Oi(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=$i(t,p),h=$i(n,p),m=h?eg(e.eachNode,[h]):[],g=new Set(m.map(x=>x.path.toLowerCase())),y=h?Hi(e.httpNode,[h]).filter(x=>!(x.source==="route"&&tg.has(x.layer??"")&&g.has(x.path.toLowerCase()))):[],A=f?Hi(e.httpAdmin,[f]):[];return sg([...A,...y,...m])},"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 h of c){let m=Oi(h.root),g=og(h.path);for(let y of p){let A=$i(m,y);!A||!l(`${g}${A}`)||f.push({path:y,source:"httpStatic",file:`${g}${A}`})}}},"checkStatic");return u(ng.map(p=>`${r}${p}`),i),u(rg.map(p=>`${o}${p}`),d),{authorization:i,metadata:d}}a(oc,"scanOAuthRouteCollisions");function ig(e){let t=a(n=>{try{return e?.[n]}catch{return}},"read");return t("_router")?.stack??t("router")?.stack??[]}a(ig,"routerStack");function ag(e){let t=e.route?.path;return t?(Array.isArray(t)?t:[t]).map(String):[]}a(ag,"layerPaths");function dg(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(dg,"middlewareHits");function Hi(e,t=["/.well-known/oauth-","/mcp/oauth/"]){let n=[];for(let r of ig(e)){if(r.handle?.oauthOwned)continue;let o=ag(r);for(let s of o)t.some(i=>Ui(s,i))&&n.push({path:s,source:"route",layer:r.name??"route"});!o.length&&dg(r,t)&&n.push({path:t.join(", "),source:"middleware",layer:r.name??"route"}),r.handle?.stack&&n.push(...Hi({router:{stack:r.handle.stack}},t))}return n}a(Hi,"detectOAuthRouteCollisionDetails");var is=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 Q.InvalidGrantError("Admin consent is required")}async challengeForAuthorizationCode(t,n){let r=this.findCode(t,n);return this.clientsStore.data.codes=this.clientsStore.data.codes.filter(o=>o!==r),this.clientsStore.persist(),this.inflight.set(r.hash,r),setTimeout(()=>this.inflight.delete(r.hash),3e4).unref?.(),r.challenge}async exchangeAuthorizationCode(t,n,r,o,s){let i=this.inflight.get(Lt(n))??this.findCode(t,n);if(this.inflight.delete(i.hash),this.clientsStore.data.codes=this.clientsStore.data.codes.filter(d=>d!==i),this.clientsStore.persist(),i.clientId!==t.client_id||i.expires<=Date.now()||o!==i.redirectUri||s&&Nt(s.href)!==i.resource)throw new Q.InvalidGrantError("Invalid authorization code");return this.issue(t.client_id,i.scopes,i.resource,i.user)}async exchangeRefreshToken(t,n,r,o){let s=this.clientsStore.data.tokens.find(l=>l.hash===Lt(n)&&l.type==="refresh"&&l.clientId===t.client_id);if(!s||s.expires<=Date.now())throw new Q.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 Q.InvalidGrantError("Refresh token reuse detected");let i=this.refreshMax()*1e3,d=s.familyStart??Date.now();if(i>0&&d+i<=Date.now())throw new Q.InvalidGrantError("Refresh token family reached its maximum lifetime");if(o&&Nt(o.href)!==s.resource)throw new Q.InvalidGrantError("Resource mismatch");if(r?.some(l=>!s.scopes.includes(l)))throw new Q.InvalidGrantError("Invalid scope");return s.rotated=!0,this.issue(t.client_id,r??s.scopes,s.resource,s.user,s.family,d)}async verifyAccessToken(t){let n=this.clientsStore.data.tokens.find(o=>o.hash===Lt(t)&&o.type==="access"&&o.expires>Date.now());if(!n||n.resource!==Nt(this.resource()))throw new Q.InvalidTokenError("Invalid access token");let r=this.clientsStore.data.clients.find(o=>o.client_id===n.clientId);if(!r)throw new Q.InvalidTokenError("Invalid client");return r.lastUsedAt=Date.now(),this.clientsStore.touch(),{token:t,clientId:n.clientId,scopes:n.scopes,expiresAt:Math.floor(n.expires/1e3),resource:new URL(n.resource),extra:{user:n.user}}}async revokeToken(t,n){let r=this.clientsStore.data.tokens.find(o=>o.hash===Lt(n.token)&&o.clientId===t.client_id);r&&(this.clientsStore.data.tokens=this.clientsStore.data.tokens.filter(o=>o.family!==r.family)),this.clientsStore.persist()}createCode(t,n,r){let o=ln();return this.clientsStore.data.codes.push({hash:Lt(o),clientId:t.client_id,challenge:n.codeChallenge,redirectUri:n.redirectUri,scopes:n.scopes??["read"],resource:this.validateResource(n.resource),expires:Date.now()+6e4,user:r}),this.clientsStore.persist(),o}validateResource(t){let n=Nt(this.resource());if(t&&Nt(t.href)!==n)throw new Q.InvalidGrantError("Resource mismatch");return n}findCode(t,n){let r=this.clientsStore.data.codes.find(o=>o.hash===Lt(n)&&o.clientId===t.client_id);if(!r||r.expires<=Date.now())throw new Q.InvalidGrantError("Invalid or expired authorization code");return r}issue(t,n,r,o,s=ln(),i=Date.now()){let d=ln(),l=ln(),c=Date.now(),u=this.refreshMax()*1e3,p=Math.min(c+this.refreshTtl()*1e3,u>0?i+u:1/0),f=this.clientsStore.data,h=f.clients.find(m=>m.client_id===t);return h&&(h.lastUsedAt=c),f.tokens.push({hash:Lt(d),type:"access",clientId:t,family:s,scopes:n,resource:r,expires:c+this.accessTtl()*1e3,user:o,rotated:!1},{hash:Lt(l),type:"refresh",clientId:t,family:s,scopes:n,resource:r,expires:p,user:o,rotated:!1,familyStart:i}),this.clientsStore.persist(),{access_token:d,token_type:"Bearer",expires_in:this.accessTtl(),refresh_token:l,scope:n.join(" ")}}};function Nt(e){let t=new URL(e);if(!["http:","https:"].includes(t.protocol)||t.username||t.password||t.search||t.hash)throw new Error("Invalid resource URL");return`${t.protocol}//${t.host}${t.pathname.replace(/\/+$/,"")}`}a(Nt,"canonicalResource");var Dn=["read","propose"];function lg(e){let t=(e??"/").split("/").filter(Boolean).join("/");return t?`/${t}/`:"/"}a(lg,"consentAdminRoot");var cg=new Set(["localhost","127.0.0.1","[::1]"]);function dn(e){return typeof e=="string"?e:""}a(dn,"value");function sc(e,t,n,r){let o=a(T=>`${n().replace(/\/$/,"")}${T}`,"abs"),s=a((T,k,_,E)=>{let v=k.url;k.url="/",T(k,_,S=>{k.url=v,E(S)})},"atRoot"),i=new Map,d=a(()=>{let T=Date.now();for(let[k,_]of i)_.expires<=T&&i.delete(k)},"sweep"),l=a(T=>{d();let k=[...i].filter(([,_])=>_.clientId===T);for(;k.length>=Xm;)i.delete(k.shift()[0]);return i.size<Zm},"reservePending"),c=a((T,k)=>{T.setHeader("Cache-Control","no-store"),T.setHeader("X-Frame-Options","DENY"),T.setHeader("Referrer-Policy","no-referrer"),T.setHeader("X-Content-Type-Options","nosniff"),T.setHeader("Content-Security-Policy",`default-src 'none'; ${k?`script-src 'nonce-${k}'; `:""}style-src 'unsafe-inline'; connect-src 'self'; form-action 'none'; base-uri 'none'; frame-ancestors 'none'`)},"securityHeaders"),u=Vr(e.settings.httpAdminRoot),p=a(T=>{if(!oe(e.settings))return[...Dn];let k=mt(T);return k==="*"?[...Dn]:k==="read"?["read"]:[]},"grantableFor"),f=a(T=>{let k=T.get("sec-fetch-site");if(k&&k!=="same-origin")return!1;let _=T.get("origin");if(!_)return!0;let E=new Set;try{E.add(new URL(n()).origin)}catch{}let v=T.get("host");return v&&(E.add(`http://${v}`),E.add(`https://${v}`)),E.has(_)},"sameOrigin"),h=a((T,k)=>{d();let _=T.query,E=t.clientsStore.getClient(dn(_.client_id));if(!E){k.status(400).send("Unknown OAuth client");return}let v=dn(_.redirect_uri)||(E.redirect_uris.length===1?E.redirect_uris[0]:"");if(!E.redirect_uris.some(ee=>(0,Xl.redirectUriMatches)(v,ee))){k.status(400).send("Unregistered redirect URI");return}if(_.response_type!=="code"||_.code_challenge_method!=="S256"||!/^[A-Za-z0-9_-]{43,128}$/.test(dn(_.code_challenge))){k.status(400).send("Authorization code with PKCE S256 is required");return}let S=[...new Set((dn(_.scope)||"read").split(" ").filter(Boolean))];if(S.some(ee=>!Dn.includes(ee))){k.status(400).send("Unsupported scope");return}let N=dn(_.resource)||r(),w;try{w=Nt(N)}catch{k.status(400).send("Invalid resource");return}if(w!==Nt(r())){k.status(400).send("Resource mismatch");return}if(!l(E.client_id)){k.status(429).send("Too many pending authorization requests; try again later");return}let F=ln();i.set(F,{clientId:E.client_id,redirect:v,challenge:dn(_.code_challenge),scopes:S,resource:N,state:dn(_.state),csrf:ln(),expires:Date.now()+10*6e4});let I=new URL(v).host,C=(0,Ir.randomBytes)(16).toString("base64");c(k,C),k.status(200).type("html").send(Jl({requestId:F,tokenKey:u,adminRoot:lg(e.settings.httpAdminRoot),nonce:C,redirectHost:I,loopbackRedirect:cg.has(new URL(v).hostname),clientName:E.client_name||"Unnamed client",scopes:S,script:Vl}))},"authorizePage"),m=a((T,k,_)=>{c(k),rc.default.json({limit:"4kb"})(T,k,()=>Ie(e,"api")(T,k,()=>{try{d();let E=/^\/mcp\/oauth\/consent\/([A-Za-z0-9_-]+)\/?$/.exec(T.path)?.[1]??"",v=i.get(E),S=v&&t.clientsStore.getClient(v.clientId);if(!v||!S){k.status(404).json({error:"Consent request expired or unknown"});return}let N=oe(e.settings)&&T.user?.username||"owner",w=v.scopes.filter(C=>p(T).includes(C));if(!w.length){k.status(403).json({error:"Your Node-RED permissions do not allow granting the requested scopes",grantable:w});return}if(T.method==="GET"){v.approver=N,k.json({client_name:S.client_name??null,redirect_host:new URL(v.redirect).host,requested:v.scopes,grantable:w,user:N,csrf:v.csrf});return}if(T.method!=="POST"){k.status(405).end();return}let F=T.body??{};if(!f(T)||F.csrf!==v.csrf||v.approver!==N){k.status(403).json({error:"Consent request verification failed"});return}i.delete(E);let I=new URL(v.redirect);if(F.decision!=="allow")I.searchParams.set("error","access_denied");else{try{t.clientsStore.approve(S.client_id)}catch{k.status(409).json({error:"Maximum active OAuth clients reached"});return}let C=t.createCode(S,{redirectUri:v.redirect,codeChallenge:v.challenge,scopes:w,resource:new URL(v.resource)},N);I.searchParams.set("code",C)}v.state&&I.searchParams.set("state",v.state),k.json({redirect:I.href})}catch(E){_(E)}}))},"consentApi"),g=(0,Zl.clientRegistrationHandler)({clientsStore:t.clientsStore,rateLimit:{windowMs:36e5,max:10,standardHeaders:!0,legacyHeaders:!1}}),y=(0,ec.tokenHandler)({provider:t}),A=(0,Ql.revocationHandler)({provider:t}),x,$="";return{server:a((T,k,_)=>{let E=T.path,v=E.replace(/\/+$/,"").toLowerCase();if(v==="/mcp/oauth/authorize"){T.method==="GET"?h(T,k,_):k.status(405).set("Allow","GET").end();return}if(v.startsWith("/mcp/oauth/consent/")){m(T,k,_);return}if(!["/mcp/oauth/token","/mcp/oauth/register","/mcp/oauth/revoke"].includes(v)){_();return}let S=n();if(S.startsWith("https://")||/^http:\/\/(localhost|127\.0\.0\.1)(:|\/|$)/.test(S)){if($!==S)try{x=(0,nc.mcpAuthRouter)({provider:t,issuerUrl:new URL(S),baseUrl:new URL(`${S.replace(/\/$/,"")}/mcp/oauth/`),resourceServerUrl:new URL(r()),scopesSupported:Dn,clientRegistrationOptions:{rateLimit:{windowMs:36e5,max:10}}}),$=S}catch{x=void 0}if(x){let N=T.url;T.url=T.url.slice(10)||"/",x(T,k,w=>{T.url=N,_(w)});return}}if(E==="/mcp/oauth/token"){s(y,T,k,_);return}if(E==="/mcp/oauth/register"){s(g,T,k,_);return}if(E==="/mcp/oauth/revoke"){s(A,T,k,_);return}_()},"server"),metadata:a((T,k,_)=>{let E=T.path;if(E==="/.well-known/oauth-authorization-server"){s((0,Di.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:Dn}),T,k,_);return}if(E==="/.well-known/oauth-protected-resource/mcp"||E==="/.well-known/oauth-protected-resource"){s((0,Di.metadataHandler)({resource:Nt(r()),authorization_servers:[n()],scopes_supported:Dn}),T,k,_);return}_()},"metadata"),auth:a((T,k,_)=>{try{let E=k.set.bind(k);k.set=((v,S)=>(v==="WWW-Authenticate"&&typeof S=="string"&&(S=S.replace('scope="read"','scope="read propose"')),typeof v=="string"?E(v,S??""):E(v))),(0,tc.requireBearerAuth)({verifier:t,requiredScopes:["read"],resourceMetadataUrl:o(e.settings.httpNodeRoot===!1||!e.httpNode?"/mcp/.well-known/oauth-protected-resource/mcp":"/.well-known/oauth-protected-resource/mcp"),expectedResource:new URL(r())})(T,k,v=>{k.set=E,_(v)})}catch(E){_(E)}},"auth")}}a(sc,"makeOAuthHandlers");var Lr=require("fs"),jn=require("path"),ic=require("url"),ac=require("yaml"),hg={};function ug(){return typeof __dirname<"u"?__dirname:(0,jn.dirname)((0,ic.fileURLToPath)(hg.url))}a(ug,"moduleDir");function dc(){let e=ug(),t=[(0,jn.join)(e,"..","docs","api","openapi.yaml"),(0,jn.join)(e,"..","..","docs","api","openapi.yaml"),(0,jn.join)(process.cwd(),"docs","api","openapi.yaml")];for(let n of t)if((0,Lr.existsSync)(n))return n;return null}a(dc,"resolveOpenApiYamlPath");function pg(){let e=dc();if(!e)throw new Error("OpenAPI specification file was not found");return(0,Lr.readFileSync)(e,"utf8")}a(pg,"loadOpenApiYaml");function lc(){return(e,t)=>{try{let n=pg();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(lc,"openApiYamlHandler");var Bi;function fg(){let e=dc();if(!e)throw new Error("OpenAPI specification file was not found");return Bi?.path!==e&&(Bi={path:e,json:JSON.stringify((0,ac.parse)((0,Lr.readFileSync)(e,"utf8")))}),Bi.json}a(fg,"loadOpenApiJson");function cc(){return(e,t)=>{try{t.status(200).type("application/json").send(fg())}catch(n){let r=n instanceof Error?n.message:String(n);t.status(500).json({error:r})}}}a(cc,"openApiJsonHandler");function mg(){return`
|
|
1163
1178
|
var settingsData = null;
|
|
1164
1179
|
var settingsSaving = false;
|
|
1165
1180
|
var settingsTimer = null;
|
|
1166
1181
|
var SETTINGS_FIELDS = {
|
|
1167
1182
|
enabled: 'enabled', allowWithoutAdminAuth: 'allow-without',
|
|
1168
1183
|
defaultLinkTtlSeconds: 'ttl', defaultDeploymentType: 'deploy',
|
|
1169
|
-
maxActiveLinks: 'max-links', publicBaseUrl: 'public-url',
|
|
1184
|
+
proposalTtlDays: 'proposal-ttl', maxActiveLinks: 'max-links', publicBaseUrl: 'public-url',
|
|
1170
1185
|
corsAllowedOrigins: 'cors', rateLimitWindowMs: 'rate-window',
|
|
1171
1186
|
rateLimitMaxPerIp: 'rate-ip', rateLimitMaxPerToken: 'rate-token'
|
|
1172
1187
|
};
|
|
@@ -1220,6 +1235,7 @@ a{color:var(--focus)}
|
|
|
1220
1235
|
$('set-deploy').value = s.defaultDeploymentType || 'nodes';
|
|
1221
1236
|
$('set-deploy-full-warn').hidden = $('set-deploy').value !== 'full';
|
|
1222
1237
|
$('set-ttl').value = String(Math.max(1, Math.round((s.defaultLinkTtlSeconds || 14400) / 3600)));
|
|
1238
|
+
$('set-proposal-ttl').value = String(s.proposalTtlDays == null ? 7 : s.proposalTtlDays);
|
|
1223
1239
|
$('set-max-links').value = s.maxActiveLinks == null ? '' : String(s.maxActiveLinks);
|
|
1224
1240
|
$('set-public-url').value = s.publicBaseUrl || '';
|
|
1225
1241
|
$('set-cors').value = (s.corsAllowedOrigins || []).join(', ');
|
|
@@ -1271,6 +1287,7 @@ a{color:var(--focus)}
|
|
|
1271
1287
|
allowWithoutAdminAuth: $('set-allow-without').checked,
|
|
1272
1288
|
defaultLinkTtlSeconds: Math.max(1, Math.floor(Number($('set-ttl').value || 4) * 3600)),
|
|
1273
1289
|
defaultDeploymentType: $('set-deploy').value,
|
|
1290
|
+
proposalTtlDays: Math.min(365, Math.max(1, Math.floor(Number($('set-proposal-ttl').value) || 7))),
|
|
1274
1291
|
maxActiveLinks: Number($('set-max-links').value),
|
|
1275
1292
|
publicBaseUrl: String($('set-public-url').value).trim(),
|
|
1276
1293
|
corsAllowedOrigins: String($('set-cors').value).split(',').map(function (x) { return x.trim(); }).filter(Boolean),
|
|
@@ -1295,7 +1312,7 @@ a{color:var(--focus)}
|
|
|
1295
1312
|
return payload;
|
|
1296
1313
|
}
|
|
1297
1314
|
|
|
1298
|
-
`}a(
|
|
1315
|
+
`}a(mg,"settingsPartForm");function gg(){return`
|
|
1299
1316
|
function secretPatternsEditable() {
|
|
1300
1317
|
return settingsCanEdit() && !fieldLocked('secretNamePatterns');
|
|
1301
1318
|
}
|
|
@@ -1383,7 +1400,7 @@ a{color:var(--focus)}
|
|
|
1383
1400
|
});
|
|
1384
1401
|
}
|
|
1385
1402
|
|
|
1386
|
-
`}a(
|
|
1403
|
+
`}a(gg,"settingsPartSecrets");function yg(){return`
|
|
1387
1404
|
function settingsFlash(msg, ok) {
|
|
1388
1405
|
var tab = document.querySelector('nav.tabs button[aria-selected="true"]');
|
|
1389
1406
|
var target = tab && tab.getAttribute('data-tab') === 'oauth' ? 'oauth-flash' : 'settings-flash';
|
|
@@ -1471,7 +1488,7 @@ a{color:var(--focus)}
|
|
|
1471
1488
|
}
|
|
1472
1489
|
}
|
|
1473
1490
|
|
|
1474
|
-
`}a(
|
|
1491
|
+
`}a(yg,"settingsPartSave");function wg(){return`
|
|
1475
1492
|
function editorNodeHref(c) {
|
|
1476
1493
|
var base = editorRoot && editorRoot !== '/' ? editorRoot.replace(/\\/$/, '') + '/' : '/';
|
|
1477
1494
|
if (c.nodeId) return base + '#node/' + encodeURIComponent(c.nodeId);
|
|
@@ -1579,7 +1596,7 @@ a{color:var(--focus)}
|
|
|
1579
1596
|
}
|
|
1580
1597
|
}
|
|
1581
1598
|
|
|
1582
|
-
`}a(
|
|
1599
|
+
`}a(wg,"settingsPartOAuth");function bg(){return`
|
|
1583
1600
|
function loadOAuthTab() {
|
|
1584
1601
|
loadSettings();
|
|
1585
1602
|
loadOAuthStatus();
|
|
@@ -1612,7 +1629,7 @@ a{color:var(--focus)}
|
|
|
1612
1629
|
showToast('Server URL copied.', true);
|
|
1613
1630
|
});
|
|
1614
1631
|
})();
|
|
1615
|
-
`}a(
|
|
1632
|
+
`}a(bg,"settingsPartWire");function uc(){return mg()+gg()+yg()+wg()+bg()}a(uc,"pageClientSettingsScript");function kg(){return`
|
|
1616
1633
|
(function () {
|
|
1617
1634
|
var root = document.getElementById('mcp-root');
|
|
1618
1635
|
if (!root) return;
|
|
@@ -1727,6 +1744,94 @@ a{color:var(--focus)}
|
|
|
1727
1744
|
return target;
|
|
1728
1745
|
}
|
|
1729
1746
|
|
|
1747
|
+
/*
|
|
1748
|
+
* Signed-out card (same look/flow as the OAuth consent page). Credentials
|
|
1749
|
+
* login: inline form \u2192 <adminRoot>/auth/token, token stored under the
|
|
1750
|
+
* editor's key (editor stays signed in). Strategy login: open the editor in
|
|
1751
|
+
* the named window; continue automatically when its token appears (storage
|
|
1752
|
+
* event) or on focus / visibility with a new token; Retry as fallback.
|
|
1753
|
+
*/
|
|
1754
|
+
var authGateShown = false;
|
|
1755
|
+
var authGateToken = null;
|
|
1756
|
+
var authGateLastAuto = 0;
|
|
1757
|
+
var authGateEditorOpened = false;
|
|
1758
|
+
function authGateAdminHref() {
|
|
1759
|
+
var root = editorLoginUrl();
|
|
1760
|
+
if (root.charAt(root.length - 1) !== '/') root += '/';
|
|
1761
|
+
return new URL(root, location.href).href;
|
|
1762
|
+
}
|
|
1763
|
+
function authGateStatus(text, isError) {
|
|
1764
|
+
var st = $('auth-gate-status');
|
|
1765
|
+
if (!st) return;
|
|
1766
|
+
st.textContent = text;
|
|
1767
|
+
st.className = 'sg-status' + (isError ? ' error' : '');
|
|
1768
|
+
st.hidden = !text;
|
|
1769
|
+
}
|
|
1770
|
+
function authGateContinue() {
|
|
1771
|
+
authGateStatus('Signed in. Loading AI Access\u2026');
|
|
1772
|
+
location.reload();
|
|
1773
|
+
}
|
|
1774
|
+
function authGateTyping() {
|
|
1775
|
+
var form = $('auth-gate-login');
|
|
1776
|
+
return !!form && !form.hidden &&
|
|
1777
|
+
!!(($('auth-gate-username') || {}).value || ($('auth-gate-password') || {}).value);
|
|
1778
|
+
}
|
|
1779
|
+
function authGateAuto(fromStorage) {
|
|
1780
|
+
if (!authGateShown) return;
|
|
1781
|
+
var tok = readEditorToken();
|
|
1782
|
+
if (!tok || tok === authGateToken) return;
|
|
1783
|
+
// Leave a half-typed form alone unless the user chose the editor handoff;
|
|
1784
|
+
// a new token written by another tab is definitive.
|
|
1785
|
+
if (!fromStorage && !authGateEditorOpened && authGateTyping()) return;
|
|
1786
|
+
if (Date.now() - authGateLastAuto < 1000) return;
|
|
1787
|
+
authGateLastAuto = Date.now();
|
|
1788
|
+
authGateContinue();
|
|
1789
|
+
}
|
|
1790
|
+
function wireAuthGate() {
|
|
1791
|
+
var form = $('auth-gate-login');
|
|
1792
|
+
if (form) form.addEventListener('submit', function (ev) {
|
|
1793
|
+
ev.preventDefault();
|
|
1794
|
+
var btn = $('auth-gate-submit');
|
|
1795
|
+
btn.disabled = true;
|
|
1796
|
+
authGateStatus('Signing in\u2026');
|
|
1797
|
+
nrPasswordSignIn(authGateAdminHref(), authTokenKey, $('auth-gate-username').value, $('auth-gate-password').value)
|
|
1798
|
+
.then(function (x) {
|
|
1799
|
+
btn.disabled = false;
|
|
1800
|
+
$('auth-gate-password').value = '';
|
|
1801
|
+
if (!x.ok) { authGateStatus('Sign in failed. Check your username and password.', true); return; }
|
|
1802
|
+
authGateContinue();
|
|
1803
|
+
})
|
|
1804
|
+
.catch(function (e) { btn.disabled = false; authGateStatus('Error: ' + (e.message || String(e)), true); });
|
|
1805
|
+
});
|
|
1806
|
+
wireAuthGateEditor();
|
|
1807
|
+
}
|
|
1808
|
+
// Both the strategy button and the optional credentials link use this handoff.
|
|
1809
|
+
function wireAuthGateEditor() {
|
|
1810
|
+
['auth-gate-editor', 'auth-gate-editor-link'].forEach(function (id) {
|
|
1811
|
+
var ed = $(id);
|
|
1812
|
+
if (!ed) return;
|
|
1813
|
+
if (id === 'auth-gate-editor-link') ed.href = editorLoginUrl();
|
|
1814
|
+
ed.addEventListener('click', function (ev) {
|
|
1815
|
+
ev.preventDefault();
|
|
1816
|
+
authGateEditorOpened = true;
|
|
1817
|
+
window.open(editorLoginUrl(), 'node-red-editor');
|
|
1818
|
+
$('auth-gate-retry').hidden = false;
|
|
1819
|
+
authGateStatus('Sign in in the Node-RED editor window. This page continues automatically.');
|
|
1820
|
+
});
|
|
1821
|
+
});
|
|
1822
|
+
var retry = $('auth-gate-retry');
|
|
1823
|
+
if (retry) retry.addEventListener('click', function () {
|
|
1824
|
+
if (readEditorToken()) authGateContinue();
|
|
1825
|
+
else authGateStatus('Not signed in yet. Sign in in the Node-RED editor, then come back here.', true);
|
|
1826
|
+
});
|
|
1827
|
+
window.addEventListener('storage', function (ev) {
|
|
1828
|
+
if (ev.key === authTokenKey && ev.newValue) authGateAuto(true);
|
|
1829
|
+
});
|
|
1830
|
+
document.addEventListener('visibilitychange', function () {
|
|
1831
|
+
if (document.visibilityState === 'visible') authGateAuto(false);
|
|
1832
|
+
});
|
|
1833
|
+
window.addEventListener('focus', function () { authGateAuto(false); });
|
|
1834
|
+
}
|
|
1730
1835
|
function showAuthGate() {
|
|
1731
1836
|
var gate = $('auth-gate');
|
|
1732
1837
|
var nav = $('main-nav');
|
|
@@ -1735,20 +1840,27 @@ a{color:var(--focus)}
|
|
|
1735
1840
|
var el = $(id);
|
|
1736
1841
|
if (el) el.hidden = true;
|
|
1737
1842
|
});
|
|
1738
|
-
if (gate)
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1843
|
+
if (!gate || authGateShown) return;
|
|
1844
|
+
authGateShown = true;
|
|
1845
|
+
authGateToken = readEditorToken();
|
|
1846
|
+
gate.hidden = false;
|
|
1847
|
+
if (document.body && document.body.classList) document.body.classList.add('signed-out');
|
|
1848
|
+
wireAuthGate();
|
|
1849
|
+
authGateStatus('Checking how Node-RED signs you in\u2026');
|
|
1850
|
+
nrLoginType(authGateAdminHref()).then(function (type) {
|
|
1851
|
+
if (type === 'credentials') {
|
|
1852
|
+
authGateStatus('Sign in with your Node-RED account to continue.');
|
|
1853
|
+
$('auth-gate-login').hidden = false;
|
|
1854
|
+
$('auth-gate-editor-link').hidden = false;
|
|
1855
|
+
$('auth-gate-username').focus();
|
|
1856
|
+
} else {
|
|
1857
|
+
authGateStatus('Sign in with the Node-RED editor in this browser. This page continues automatically.');
|
|
1858
|
+
$('auth-gate-editor').hidden = false;
|
|
1859
|
+
$('auth-gate-retry').hidden = false;
|
|
1747
1860
|
}
|
|
1748
|
-
}
|
|
1749
|
-
showFlash($('boot-flash'), 'Please sign in to Node-RED first.', false);
|
|
1861
|
+
});
|
|
1750
1862
|
}
|
|
1751
|
-
|
|
1863
|
+
${os}
|
|
1752
1864
|
function isStatusPath(path) {
|
|
1753
1865
|
return path === '/status' || path.indexOf('/status?') === 0;
|
|
1754
1866
|
}
|
|
@@ -1881,7 +1993,7 @@ a{color:var(--focus)}
|
|
|
1881
1993
|
}
|
|
1882
1994
|
return body;
|
|
1883
1995
|
}
|
|
1884
|
-
`}a(
|
|
1996
|
+
`}a(kg,"pageClientScriptPartA");function Ag(){return`
|
|
1885
1997
|
// Time helpers \u2014 keep in sync with page-time.ts (always browser-local Intl).
|
|
1886
1998
|
function titleFromDisplayName(displayName) {
|
|
1887
1999
|
var s = String(displayName || '');
|
|
@@ -1988,7 +2100,7 @@ a{color:var(--focus)}
|
|
|
1988
2100
|
btn.disabled = false;
|
|
1989
2101
|
}, 1600);
|
|
1990
2102
|
}
|
|
1991
|
-
`}a(
|
|
2103
|
+
`}a(Ag,"pageClientScriptPartATimeCopy");function Sg(){return`
|
|
1992
2104
|
function shortId(value, keep) {
|
|
1993
2105
|
var s = String(value == null ? '' : value);
|
|
1994
2106
|
var n = keep || 8;
|
|
@@ -2061,6 +2173,9 @@ a{color:var(--focus)}
|
|
|
2061
2173
|
'proposal.approved': 'Approved',
|
|
2062
2174
|
'proposal.rejected': 'Rejected',
|
|
2063
2175
|
'proposal.failed': 'Failed',
|
|
2176
|
+
'proposal.conflict': 'Conflict',
|
|
2177
|
+
'proposal.resolved': 'Resolved',
|
|
2178
|
+
'proposal.conflict_resolved': 'Conflict resolved',
|
|
2064
2179
|
'proposal.withdrawn': 'Withdrawn',
|
|
2065
2180
|
'link.created': 'Link created',
|
|
2066
2181
|
'link.revoked': 'Link revoked'
|
|
@@ -2079,6 +2194,9 @@ a{color:var(--focus)}
|
|
|
2079
2194
|
'proposal.approved': 'Approved',
|
|
2080
2195
|
'proposal.rejected': 'Rejected',
|
|
2081
2196
|
'proposal.failed': 'Failed',
|
|
2197
|
+
'proposal.conflict': 'Conflict',
|
|
2198
|
+
'proposal.resolved': 'Resolved',
|
|
2199
|
+
'proposal.conflict_resolved': 'Conflict resolved',
|
|
2082
2200
|
'proposal.withdrawn': 'Withdrawn',
|
|
2083
2201
|
'link.created': 'Link created',
|
|
2084
2202
|
'link.revoked': 'Link revoked'
|
|
@@ -2118,7 +2236,7 @@ a{color:var(--focus)}
|
|
|
2118
2236
|
}
|
|
2119
2237
|
return '';
|
|
2120
2238
|
}
|
|
2121
|
-
`}a(
|
|
2239
|
+
`}a(Sg,"pageClientScriptPartAFormat");function xg(){return`
|
|
2122
2240
|
function lookupCachedProposal(id) {
|
|
2123
2241
|
if (!id) return null;
|
|
2124
2242
|
return cachedProposals.filter(function (x) { return x.id === id; })[0] || null;
|
|
@@ -2239,7 +2357,7 @@ a{color:var(--focus)}
|
|
|
2239
2357
|
return k + '=' + String(v);
|
|
2240
2358
|
}).join(', '));
|
|
2241
2359
|
}
|
|
2242
|
-
`}a(
|
|
2360
|
+
`}a(xg,"pageClientScriptPartAAudit");function Tg(){return`
|
|
2243
2361
|
// Hash helpers \u2014 keep in sync with page-hash.ts
|
|
2244
2362
|
var TAB_IDS = ['status', 'settings', 'links', 'oauth', 'proposals'];
|
|
2245
2363
|
function hashForTab(tab) {
|
|
@@ -2525,7 +2643,7 @@ a{color:var(--focus)}
|
|
|
2525
2643
|
}
|
|
2526
2644
|
}
|
|
2527
2645
|
|
|
2528
|
-
`}a(
|
|
2646
|
+
`}a(Tg,"pageClientScriptPartATabs");function Rg(){return`
|
|
2529
2647
|
async function loadLinks(opts) {
|
|
2530
2648
|
opts = opts || {};
|
|
2531
2649
|
if (!aiOn || !canManage) {
|
|
@@ -2599,7 +2717,7 @@ a{color:var(--focus)}
|
|
|
2599
2717
|
showToast(e.message || String(e), false);
|
|
2600
2718
|
}
|
|
2601
2719
|
}
|
|
2602
|
-
`}a(
|
|
2720
|
+
`}a(Rg,"pageClientScriptPartA2");function Pg(){return`
|
|
2603
2721
|
|
|
2604
2722
|
function normalizeLinkKind(kind) {
|
|
2605
2723
|
if (kind === 'rest' || kind === 'mcp' || kind === 'get_link') return kind;
|
|
@@ -3177,16 +3295,16 @@ a{color:var(--focus)}
|
|
|
3177
3295
|
}
|
|
3178
3296
|
|
|
3179
3297
|
function proposalIsStale(p) {
|
|
3180
|
-
if (!p || p.status !== 'pending') return false;
|
|
3181
|
-
if (p.baseRevStale === true) return true;
|
|
3298
|
+
if (!p || (p.status !== 'pending' && p.status !== 'conflict')) return false;
|
|
3182
3299
|
var cached = proposalDiffCache[p.id];
|
|
3183
|
-
|
|
3300
|
+
var rebase = (cached && cached.rebase) || p.rebase;
|
|
3301
|
+
return p.status === 'conflict' || !!(rebase && rebase.state === 'conflict');
|
|
3184
3302
|
}
|
|
3185
3303
|
|
|
3186
3304
|
function formatPendingBadgeText(live, stale) {
|
|
3187
3305
|
var parts = [];
|
|
3188
3306
|
if (live > 0) parts.push(live + ' to review');
|
|
3189
|
-
if (stale > 0) parts.push(stale + '
|
|
3307
|
+
if (stale > 0) parts.push(stale + ' conflict' + (stale === 1 ? '' : 's'));
|
|
3190
3308
|
return parts.length ? parts.join(' \xB7 ') : '0';
|
|
3191
3309
|
}
|
|
3192
3310
|
|
|
@@ -3317,7 +3435,7 @@ a{color:var(--focus)}
|
|
|
3317
3435
|
}
|
|
3318
3436
|
|
|
3319
3437
|
function countPendingBuckets() {
|
|
3320
|
-
var pending = cachedProposals.filter(function (p) { return p.status === 'pending'; });
|
|
3438
|
+
var pending = cachedProposals.filter(function (p) { return (p.status === 'pending' || p.status === 'conflict'); });
|
|
3321
3439
|
var stale = 0;
|
|
3322
3440
|
pending.forEach(function (p) {
|
|
3323
3441
|
if (proposalIsStale(p)) stale += 1;
|
|
@@ -3326,10 +3444,10 @@ a{color:var(--focus)}
|
|
|
3326
3444
|
pending: pending.length,
|
|
3327
3445
|
live: pending.length - stale,
|
|
3328
3446
|
stale: stale,
|
|
3329
|
-
decided: Math.max(decidedTotal, cachedProposals.filter(function (p) { return p.status !== 'pending'; }).length)
|
|
3447
|
+
decided: Math.max(decidedTotal, cachedProposals.filter(function (p) { return (p.status !== 'pending' && p.status !== 'conflict'); }).length)
|
|
3330
3448
|
};
|
|
3331
3449
|
}
|
|
3332
|
-
`}a(
|
|
3450
|
+
`}a(Pg,"pageClientScriptPartB");function _g(){return`
|
|
3333
3451
|
var DIFF_MODE_KEY = 'ai-access-diff-mode';
|
|
3334
3452
|
var DIFF_SPLIT_MIN_PX = ${900};
|
|
3335
3453
|
var PRESENCE_SHORT_TEXT_MAX = ${120};
|
|
@@ -3446,7 +3564,7 @@ a{color:var(--focus)}
|
|
|
3446
3564
|
'</div>';
|
|
3447
3565
|
}
|
|
3448
3566
|
|
|
3449
|
-
`}a(
|
|
3567
|
+
`}a(_g,"pageClientScriptPartBDiff");function Eg(){return`
|
|
3450
3568
|
function isCollapsiblePresenceText(value) {
|
|
3451
3569
|
var s = String(value == null ? '' : value);
|
|
3452
3570
|
if (s.indexOf('\\n') >= 0) return true;
|
|
@@ -3549,7 +3667,7 @@ a{color:var(--focus)}
|
|
|
3549
3667
|
});
|
|
3550
3668
|
});
|
|
3551
3669
|
}
|
|
3552
|
-
`}a(
|
|
3670
|
+
`}a(Eg,"pageClientScriptPartBDiffPresence");function Cg(){return`
|
|
3553
3671
|
function textChangeLineCount(ch) {
|
|
3554
3672
|
return (ch.lines || []).length;
|
|
3555
3673
|
}
|
|
@@ -3608,9 +3726,10 @@ a{color:var(--focus)}
|
|
|
3608
3726
|
'<code class="diff-before" title="before">' + esc(JSON.stringify(ch.before)) + '</code>' +
|
|
3609
3727
|
' \u2192 <code class="diff-after" title="after">' + esc(JSON.stringify(ch.after)) + '</code></div>';
|
|
3610
3728
|
}
|
|
3611
|
-
`}a(
|
|
3729
|
+
`}a(Cg,"pageClientScriptPartBDiffView");function Ig(){return`
|
|
3612
3730
|
function renderDiffSections(sections, mode, emptyMsg) {
|
|
3613
|
-
var parts =
|
|
3731
|
+
var parts = sections.summary && (sections.summary.tabChanges || []).length
|
|
3732
|
+
? '<p>Flow settings changed: ' + esc(sections.summary.tabChanges.join(', ')) + '</p>' : '';
|
|
3614
3733
|
if (sections.added && sections.added.length) {
|
|
3615
3734
|
parts += '<div class="diff-section"><h4>Added nodes</h4>' +
|
|
3616
3735
|
sections.added.map(function (n) { return renderPresenceNode(n, 'added'); }).join('') +
|
|
@@ -3651,7 +3770,8 @@ a{color:var(--focus)}
|
|
|
3651
3770
|
? '<p class="muted">No starting snapshot was saved with this proposal, so changes are compared with the live flow.</p>'
|
|
3652
3771
|
: '';
|
|
3653
3772
|
// Hide +/-/~ counters and tab:label jargon \u2014 the card sentence already says it.
|
|
3654
|
-
var showModeToggle = diffHasTextChanges(diff)
|
|
3773
|
+
var showModeToggle = diffHasTextChanges(diff) || diffHasTextChanges(diff.liveChanges || {}) ||
|
|
3774
|
+
diffHasTextChanges(diff.vsCurrent || {});
|
|
3655
3775
|
var toolbar = '';
|
|
3656
3776
|
if (showModeToggle) {
|
|
3657
3777
|
toolbar =
|
|
@@ -3675,16 +3795,51 @@ a{color:var(--focus)}
|
|
|
3675
3795
|
? 'No node changes compared with what the AI started from.'
|
|
3676
3796
|
: 'No node changes compared with the live flow.'
|
|
3677
3797
|
);
|
|
3678
|
-
var
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3798
|
+
var liveChanges = diff.liveChanges;
|
|
3799
|
+
var liveHtml = liveChanges && ((liveChanges.added || []).length ||
|
|
3800
|
+
(liveChanges.removed || []).length || (liveChanges.modified || []).length ||
|
|
3801
|
+
(liveChanges.summary && (liveChanges.summary.tabChanges || []).length))
|
|
3802
|
+
? '<h3>Changed in editor since submit</h3>' + renderDiffSections(liveChanges, mode, 'No editor node changes.') : '';
|
|
3803
|
+
var deployHtml = diff.vsCurrent
|
|
3804
|
+
? renderDiffSections(diff.vsCurrent, mode, 'No node changes compared with the live flow.')
|
|
3805
|
+
: '<p class="muted">' + (diff.rebase && diff.rebase.state === 'conflict'
|
|
3806
|
+
? 'Resolve conflicts to preview what will deploy.' : 'Deployment preview unavailable.') + '</p>';
|
|
3807
|
+
return missing + warnHtml + toolbar + '<h3>Proposed change (vs base)</h3>' + sections +
|
|
3808
|
+
liveHtml + '<h3>Will deploy (vs live)</h3>' + deployHtml;
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3811
|
+
function renderConflictPanel(p, diff) {
|
|
3812
|
+
var rebase = (diff && diff.rebase) || p.rebase;
|
|
3813
|
+
if (!rebase || rebase.state !== 'conflict') return '';
|
|
3814
|
+
return '<div class="proposal-conflicts" role="region" aria-label="Proposal conflicts">' +
|
|
3815
|
+
'<h3>Conflicts</h3><p>Resolve these conflicts before approving.</p>' +
|
|
3816
|
+
(rebase.conflicts || []).map(function (c) {
|
|
3817
|
+
return '<div class="proposal-conflict"><strong>' + esc(c.nodeName || 'Unnamed node') + '</strong>' +
|
|
3818
|
+
' \xB7 ' + esc(c.nodeType || 'unknown type') + ' \xB7 <code>' + esc(c.nodeId || '') + '</code>' +
|
|
3819
|
+
'<p>' + esc(c.kind) + '</p><p>Fields: ' + esc((c.fields || []).join(', ')) + '</p>' +
|
|
3820
|
+
'<p>' + esc(c.message) + '</p>' + (canApprove ? (c.choices || []).filter(function (choice) {
|
|
3821
|
+
return choice === 'live' || choice === 'proposed';
|
|
3822
|
+
}).map(function (choice) {
|
|
3823
|
+
return '<button type="button" class="action secondary resolve-conflict-btn" data-id="' + esc(p.id) +
|
|
3824
|
+
'" data-node-id="' + esc(c.nodeId || '') + '" data-choice="' + esc(choice) + '">' +
|
|
3825
|
+
(choice === 'live' ? 'Keep live' : 'Use proposed') + '</button> ';
|
|
3826
|
+
}).join('') : '') + '</div>';
|
|
3827
|
+
}).join('') + (canApprove ? '<button type="button" class="action secondary recheck-btn" data-id="' +
|
|
3828
|
+
esc(p.id) + '">Re-check</button>' : '') + '</div>';
|
|
3829
|
+
}
|
|
3830
|
+
|
|
3831
|
+
async function updateProposalConflict(id, nodeId, choice) {
|
|
3832
|
+
try {
|
|
3833
|
+
await api('/proposals/' + encodeURIComponent(id) + (choice ? '/resolve-conflict' : '/recheck'), {
|
|
3834
|
+
method: 'POST', body: JSON.stringify(choice ? { nodeId: nodeId, choice: choice } : {})
|
|
3835
|
+
});
|
|
3836
|
+
delete proposalDiffCache[id];
|
|
3837
|
+
await loadProposals();
|
|
3838
|
+
await loadProposalDiff(id);
|
|
3839
|
+
notifySidebarPendingRefresh();
|
|
3840
|
+
} catch (e) {
|
|
3841
|
+
showFlash($('proposals-flash'), e.message || String(e), false);
|
|
3686
3842
|
}
|
|
3687
|
-
return missing + warnHtml + toolbar + sections + vsCurrent;
|
|
3688
3843
|
}
|
|
3689
3844
|
|
|
3690
3845
|
function renderDiffRevMeta(diff) {
|
|
@@ -3697,14 +3852,14 @@ a{color:var(--focus)}
|
|
|
3697
3852
|
// Full view kept for mode-toggle re-render inside .proposal-changes.
|
|
3698
3853
|
return renderHumanDiff(diff, mode);
|
|
3699
3854
|
}
|
|
3700
|
-
`}a(
|
|
3855
|
+
`}a(Ig,"pageClientScriptPartBDiffView2");function Lg(){return`
|
|
3701
3856
|
function updatePendingBadges() {
|
|
3702
3857
|
var counts = countPendingBuckets();
|
|
3703
3858
|
var fullText = formatPendingBadgeText(counts.live, counts.stale);
|
|
3704
3859
|
var badge = $('proposals-pending-badge');
|
|
3705
3860
|
if (badge) {
|
|
3706
|
-
//
|
|
3707
|
-
badge.textContent = String(counts.
|
|
3861
|
+
// Include conflicts in the open proposal count.
|
|
3862
|
+
badge.textContent = String(counts.pending);
|
|
3708
3863
|
badge.hidden = counts.pending <= 0;
|
|
3709
3864
|
badge.title = fullText;
|
|
3710
3865
|
}
|
|
@@ -3752,6 +3907,14 @@ a{color:var(--focus)}
|
|
|
3752
3907
|
});
|
|
3753
3908
|
});
|
|
3754
3909
|
});
|
|
3910
|
+
list.querySelectorAll('.resolve-conflict-btn, .recheck-btn').forEach(function (btn) {
|
|
3911
|
+
bindOnce(btn, 'click', async function () {
|
|
3912
|
+
if (btn.disabled) return;
|
|
3913
|
+
btn.disabled = true;
|
|
3914
|
+
await updateProposalConflict(btn.getAttribute('data-id'), btn.getAttribute('data-node-id'), btn.getAttribute('data-choice'));
|
|
3915
|
+
btn.disabled = false;
|
|
3916
|
+
});
|
|
3917
|
+
});
|
|
3755
3918
|
list.querySelectorAll('.approve-btn').forEach(function (btn) {
|
|
3756
3919
|
bindOnce(btn, 'click', function () {
|
|
3757
3920
|
if (btn.disabled) return;
|
|
@@ -3763,24 +3926,6 @@ a{color:var(--focus)}
|
|
|
3763
3926
|
openRejectDialog(btn.getAttribute('data-id'));
|
|
3764
3927
|
});
|
|
3765
3928
|
});
|
|
3766
|
-
list.querySelectorAll('.dismiss-stale-btn').forEach(function (btn) {
|
|
3767
|
-
bindOnce(btn, 'click', function () {
|
|
3768
|
-
var id = btn.getAttribute('data-id');
|
|
3769
|
-
openConfirmDialog({
|
|
3770
|
-
title: 'Dismiss proposal',
|
|
3771
|
-
message: 'This closes the out-of-date item from your queue. The flow is unchanged.',
|
|
3772
|
-
confirmLabel: 'Dismiss',
|
|
3773
|
-
danger: true,
|
|
3774
|
-
onConfirm: function () {
|
|
3775
|
-
rejectProposal(id, 'Dismissed \u2014 ask the AI to resubmit');
|
|
3776
|
-
}
|
|
3777
|
-
});
|
|
3778
|
-
});
|
|
3779
|
-
});
|
|
3780
|
-
var dismissAll = list.querySelector('#dismiss-all-stale');
|
|
3781
|
-
bindOnce(dismissAll, 'click', function () {
|
|
3782
|
-
dismissAllStaleProposals();
|
|
3783
|
-
});
|
|
3784
3929
|
list.querySelectorAll('.deploy-type').forEach(function (sel) {
|
|
3785
3930
|
bindOnce(sel, 'change', function () {
|
|
3786
3931
|
var warn = list.querySelector('.full-deploy-warn[data-id="' + sel.getAttribute('data-id') + '"]');
|
|
@@ -3813,7 +3958,7 @@ a{color:var(--focus)}
|
|
|
3813
3958
|
});
|
|
3814
3959
|
});
|
|
3815
3960
|
}
|
|
3816
|
-
`}a(
|
|
3961
|
+
`}a(Lg,"pageClientScriptPartB2");function Ng(){return`
|
|
3817
3962
|
function renderDeleteBanner(diff) {
|
|
3818
3963
|
if (!isLargeDelete(diff)) return '';
|
|
3819
3964
|
var beforeN = diff.summary.beforeNodeCount || 0;
|
|
@@ -3823,9 +3968,7 @@ a{color:var(--focus)}
|
|
|
3823
3968
|
}
|
|
3824
3969
|
|
|
3825
3970
|
function renderPendingActions(p, stale) {
|
|
3826
|
-
if (!canApprove || p.status !== 'pending') return '';
|
|
3827
|
-
// Stale items live in the Out of date group (Dismiss only).
|
|
3828
|
-
if (stale) return '';
|
|
3971
|
+
if (!canApprove || (p.status !== 'pending' && p.status !== 'conflict')) return '';
|
|
3829
3972
|
var cached = proposalDiffCache[p.id];
|
|
3830
3973
|
var warns = proposalWarnings(p, cached);
|
|
3831
3974
|
var warnNote = warns.length
|
|
@@ -3839,7 +3982,7 @@ a{color:var(--focus)}
|
|
|
3839
3982
|
'<p class="muted approve-help">Approve deploys this change to Node-RED. Nothing is live until you approve.</p>' +
|
|
3840
3983
|
'<div class="row">' +
|
|
3841
3984
|
'<button type="button" class="action approve-btn" data-id="' + esc(p.id) +
|
|
3842
|
-
'">Approve & deploy</button>' +
|
|
3985
|
+
'"' + (stale ? ' disabled title="Resolve conflicts before approving"' : (!cached ? ' disabled title="Wait for the diff to load"' : '')) + '>Approve & deploy</button>' +
|
|
3843
3986
|
'<button type="button" class="action secondary reject-btn" data-id="' + esc(p.id) +
|
|
3844
3987
|
'">Reject</button>' +
|
|
3845
3988
|
'</div>' +
|
|
@@ -3859,43 +4002,7 @@ a{color:var(--focus)}
|
|
|
3859
4002
|
'</details>';
|
|
3860
4003
|
}
|
|
3861
4004
|
|
|
3862
|
-
|
|
3863
|
-
var staleIds = cachedProposals
|
|
3864
|
-
.filter(function (p) { return p.status === 'pending' && proposalIsStale(p); })
|
|
3865
|
-
.map(function (p) { return p.id; });
|
|
3866
|
-
if (!staleIds.length) return;
|
|
3867
|
-
var n = staleIds.length;
|
|
3868
|
-
openConfirmDialog({
|
|
3869
|
-
title: 'Dismiss out-of-date proposals',
|
|
3870
|
-
message: 'Dismiss ' + n + ' out-of-date proposal' + (n === 1 ? '' : 's') +
|
|
3871
|
-
' from your queue? The flow is unchanged.',
|
|
3872
|
-
confirmLabel: n === 1 ? 'Dismiss' : 'Dismiss all',
|
|
3873
|
-
danger: true,
|
|
3874
|
-
onConfirm: function () { doDismissAllStaleProposals(staleIds); }
|
|
3875
|
-
});
|
|
3876
|
-
}
|
|
3877
|
-
|
|
3878
|
-
async function doDismissAllStaleProposals(staleIds) {
|
|
3879
|
-
var flash = $('proposals-flash');
|
|
3880
|
-
var failed = 0;
|
|
3881
|
-
for (var i = 0; i < staleIds.length; i += 1) {
|
|
3882
|
-
try {
|
|
3883
|
-
await rejectProposal(staleIds[i], 'Dismissed \u2014 ask the AI to resubmit', { silent: true });
|
|
3884
|
-
} catch (e) {
|
|
3885
|
-
failed += 1;
|
|
3886
|
-
}
|
|
3887
|
-
}
|
|
3888
|
-
if (failed) {
|
|
3889
|
-
showFlash(flash, 'Dismissed with ' + failed + ' error(s).', false);
|
|
3890
|
-
} else {
|
|
3891
|
-
showFlash(flash, 'Dismissed ' + staleIds.length + ' out-of-date proposal' +
|
|
3892
|
-
(staleIds.length === 1 ? '' : 's') + '.', true);
|
|
3893
|
-
showToast('Out-of-date proposals dismissed.', true);
|
|
3894
|
-
}
|
|
3895
|
-
loadProposals();
|
|
3896
|
-
loadLogs();
|
|
3897
|
-
}
|
|
3898
|
-
`}a(Qm,"pageClientScriptPartB2Actions");function eh(){return`
|
|
4005
|
+
`}a(Ng,"pageClientScriptPartB2Actions");function Fg(){return`
|
|
3899
4006
|
function renderRequestedVia(p) {
|
|
3900
4007
|
if (!p.createdBy) return '';
|
|
3901
4008
|
if (p.createdBy.label) {
|
|
@@ -3946,6 +4053,13 @@ a{color:var(--focus)}
|
|
|
3946
4053
|
'</span>';
|
|
3947
4054
|
}
|
|
3948
4055
|
|
|
4056
|
+
function renderRebaseChip(p, diff) {
|
|
4057
|
+
var rebase = (diff && diff.rebase) || p.rebase;
|
|
4058
|
+
var count = p.rebase && p.rebase.liveChangedCount;
|
|
4059
|
+
return '<span class="rebase-chip"' + (p.status === 'pending' && rebase && rebase.state === 'merged' ? '' : ' hidden') +
|
|
4060
|
+
'>Rebased \xB7 ' + esc(count == null ? 0 : count) + ' live changes</span>';
|
|
4061
|
+
}
|
|
4062
|
+
|
|
3949
4063
|
function renderProposalCard(p) {
|
|
3950
4064
|
var cached = proposalDiffCache[p.id];
|
|
3951
4065
|
var title = titleForProposalCard(p);
|
|
@@ -3959,7 +4073,13 @@ a{color:var(--focus)}
|
|
|
3959
4073
|
var deleteBanner = cached ? renderDeleteBanner(cached) : '';
|
|
3960
4074
|
// Card-level banner even before diff loads (warnings come from list API).
|
|
3961
4075
|
var cardWarns = proposalWarnings(p, cached);
|
|
3962
|
-
var
|
|
4076
|
+
var deployBanner = p.deployPending
|
|
4077
|
+
? '<div class="proposal-warnings" role="status"><p class="proposal-warnings-title">Deploy outcome unknown</p><p>' +
|
|
4078
|
+
esc(p.deployPending.queueReleased
|
|
4079
|
+
? 'The approval queue was released. Check the live flow before approving again.'
|
|
4080
|
+
: p.deployPending.reason) + '</p></div>'
|
|
4081
|
+
: '';
|
|
4082
|
+
var warnBanner = deployBanner + renderRestoredSecrets(p) + (cardWarns.length && !cached
|
|
3963
4083
|
? renderValidationWarnings(cardWarns)
|
|
3964
4084
|
: '');
|
|
3965
4085
|
var stale = proposalIsStale(p);
|
|
@@ -3973,8 +4093,8 @@ a{color:var(--focus)}
|
|
|
3973
4093
|
'" data-id="' + esc(p.id) + '" data-status="' + esc(p.status) + '">' +
|
|
3974
4094
|
'<header class="proposal-card-head">' +
|
|
3975
4095
|
'<h3 class="proposal-heading" data-title-for="' + esc(p.id) + '">' + esc(title) + '</h3>' +
|
|
3976
|
-
'<span class="status-pill ' + esc(p.status) + '">' + esc(p.status) + '</span> ' +
|
|
3977
|
-
renderProposalMetaLine(p) +
|
|
4096
|
+
'<span class="status-pill ' + esc(p.status) + '">' + esc(p.status === 'conflict' ? 'Conflict' : p.status) + '</span> ' +
|
|
4097
|
+
renderRebaseChip(p, cached) + renderProposalMetaLine(p) +
|
|
3978
4098
|
'</header>' +
|
|
3979
4099
|
(createdBy ? createdBy + '<br/>' : '') +
|
|
3980
4100
|
'<p class="proposal-plain-summary" data-plain-for="' + esc(p.id) + '">' +
|
|
@@ -3982,58 +4102,13 @@ a{color:var(--focus)}
|
|
|
3982
4102
|
deleteBanner +
|
|
3983
4103
|
warnBanner +
|
|
3984
4104
|
reason +
|
|
4105
|
+
'<div class="proposal-conflict-host">' + renderConflictPanel(p, cached) + '</div>' +
|
|
3985
4106
|
changes +
|
|
3986
4107
|
renderProposalTech(p, cached) +
|
|
3987
4108
|
actions +
|
|
3988
4109
|
'</article>';
|
|
3989
4110
|
}
|
|
3990
|
-
`}a(
|
|
3991
|
-
function renderStaleItem(p) {
|
|
3992
|
-
var cached = proposalDiffCache[p.id];
|
|
3993
|
-
var title = titleForProposalCard(p);
|
|
3994
|
-
var deleteBanner = cached ? renderDeleteBanner(cached) : '';
|
|
3995
|
-
var dismissBtn = canApprove
|
|
3996
|
-
? '<button type="button" class="action secondary dismiss-stale-btn" data-id="' +
|
|
3997
|
-
esc(p.id) + '">Dismiss</button>'
|
|
3998
|
-
: '';
|
|
3999
|
-
return '<div class="stale-item proposal-detail" id="proposal-' + esc(p.id) +
|
|
4000
|
-
'" data-id="' + esc(p.id) + '" data-status="pending" data-stale="1">' +
|
|
4001
|
-
'<div class="stale-item-row">' +
|
|
4002
|
-
'<div class="stale-item-main">' +
|
|
4003
|
-
'<strong class="proposal-heading" data-title-for="' + esc(p.id) + '">' + esc(title) +
|
|
4004
|
-
'</strong>' +
|
|
4005
|
-
'<span class="stale-reason muted">Flow changed since this was submitted</span>' +
|
|
4006
|
-
renderProposalMetaLine(p) +
|
|
4007
|
-
'</div>' +
|
|
4008
|
-
dismissBtn +
|
|
4009
|
-
'</div>' +
|
|
4010
|
-
deleteBanner +
|
|
4011
|
-
'<details class="stale-item-diff">' +
|
|
4012
|
-
'<summary>Show changes</summary>' +
|
|
4013
|
-
'<div class="proposal-changes proposal-diff" data-diff-for="' + esc(p.id) +
|
|
4014
|
-
'" data-changes-for="' + esc(p.id) + '">' +
|
|
4015
|
-
(cached ? renderHumanDiff(cached) : '<p class="muted">Loading changes\u2026</p>') +
|
|
4016
|
-
'</div>' +
|
|
4017
|
-
renderProposalTech(p, cached) +
|
|
4018
|
-
'</details></div>';
|
|
4019
|
-
}
|
|
4020
|
-
|
|
4021
|
-
function renderStaleGroup(items) {
|
|
4022
|
-
if (!items.length) return '';
|
|
4023
|
-
return '<details class="stale-group">' +
|
|
4024
|
-
'<summary>Out of date (' + items.length + ')</summary>' +
|
|
4025
|
-
'<p class="muted dismiss-vs-reject">Dismiss closes an out-of-date item from your queue ' +
|
|
4026
|
-
'(the AI is asked to resubmit). Reject tells the AI the change was refused \u2014 use Reject ' +
|
|
4027
|
-
'only on proposals that are still current.</p>' +
|
|
4028
|
-
(canApprove
|
|
4029
|
-
? '<div class="stale-group-actions">' +
|
|
4030
|
-
'<button type="button" class="action secondary" id="dismiss-all-stale">' +
|
|
4031
|
-
'Dismiss all out-of-date</button></div>'
|
|
4032
|
-
: '') +
|
|
4033
|
-
items.map(renderStaleItem).join('') +
|
|
4034
|
-
'</details>';
|
|
4035
|
-
}
|
|
4036
|
-
|
|
4111
|
+
`}a(Fg,"pageClientScriptPartB2Cards");function Og(){return`
|
|
4037
4112
|
function renderCompactProposal(p) {
|
|
4038
4113
|
var title = titleForProposalCard(p);
|
|
4039
4114
|
return '<div class="proposal-card-compact" data-id="' + esc(p.id) +
|
|
@@ -4066,8 +4141,8 @@ a{color:var(--focus)}
|
|
|
4066
4141
|
}
|
|
4067
4142
|
|
|
4068
4143
|
function countSearchMatchesInFilter(filter) {
|
|
4069
|
-
var pending = cachedProposals.filter(function (p) { return p.status === 'pending'; });
|
|
4070
|
-
var decided = cachedProposals.filter(function (p) { return p.status !== 'pending'; });
|
|
4144
|
+
var pending = cachedProposals.filter(function (p) { return (p.status === 'pending' || p.status === 'conflict'); });
|
|
4145
|
+
var decided = cachedProposals.filter(function (p) { return (p.status !== 'pending' && p.status !== 'conflict'); });
|
|
4071
4146
|
if (filter === 'decided') {
|
|
4072
4147
|
return decided.filter(proposalMatchesSearch).length;
|
|
4073
4148
|
}
|
|
@@ -4105,8 +4180,8 @@ a{color:var(--focus)}
|
|
|
4105
4180
|
var list = $('proposals-list');
|
|
4106
4181
|
if (!list) return;
|
|
4107
4182
|
var filters = $('proposals-filters');
|
|
4108
|
-
var pending = cachedProposals.filter(function (p) { return p.status === 'pending'; });
|
|
4109
|
-
var decided = cachedProposals.filter(function (p) { return p.status !== 'pending'; });
|
|
4183
|
+
var pending = cachedProposals.filter(function (p) { return (p.status === 'pending' || p.status === 'conflict'); });
|
|
4184
|
+
var decided = cachedProposals.filter(function (p) { return (p.status !== 'pending' && p.status !== 'conflict'); });
|
|
4110
4185
|
updatePendingBadges();
|
|
4111
4186
|
if (filters) filters.hidden = cachedProposals.length === 0;
|
|
4112
4187
|
if (!cachedProposals.length) {
|
|
@@ -4117,13 +4192,8 @@ a{color:var(--focus)}
|
|
|
4117
4192
|
return;
|
|
4118
4193
|
}
|
|
4119
4194
|
if (proposalsFilter === 'pending') {
|
|
4120
|
-
var live = pending.filter(
|
|
4121
|
-
|
|
4122
|
-
});
|
|
4123
|
-
var stale = pending.filter(function (p) {
|
|
4124
|
-
return proposalIsStale(p) && proposalMatchesSearch(p);
|
|
4125
|
-
});
|
|
4126
|
-
if (!live.length && !stale.length) {
|
|
4195
|
+
var live = pending.filter(proposalMatchesSearch);
|
|
4196
|
+
if (!live.length) {
|
|
4127
4197
|
var decidedMatches = countSearchMatchesInFilter('decided');
|
|
4128
4198
|
list.innerHTML = renderEmptyProposalsState(
|
|
4129
4199
|
proposalsSearchQuery.trim() ? 'No matching proposals' : 'No pending proposals',
|
|
@@ -4135,9 +4205,9 @@ a{color:var(--focus)}
|
|
|
4135
4205
|
bindCrossTabHint(list);
|
|
4136
4206
|
return;
|
|
4137
4207
|
}
|
|
4138
|
-
disambiguatedTitleCache = buildDisambiguatedTitles(live
|
|
4139
|
-
list.innerHTML = live.map(renderProposalCard).join('')
|
|
4140
|
-
live.
|
|
4208
|
+
disambiguatedTitleCache = buildDisambiguatedTitles(live);
|
|
4209
|
+
list.innerHTML = live.map(renderProposalCard).join('');
|
|
4210
|
+
live.forEach(function (p) { loadProposalDiff(p.id); });
|
|
4141
4211
|
} else {
|
|
4142
4212
|
var matchedDecided = decided.filter(proposalMatchesSearch);
|
|
4143
4213
|
if (proposalsSearchQuery.trim() && decidedHasMore) {
|
|
@@ -4162,7 +4232,7 @@ a{color:var(--focus)}
|
|
|
4162
4232
|
bindProposalActions(list);
|
|
4163
4233
|
focusProposalFromHash();
|
|
4164
4234
|
}
|
|
4165
|
-
`}a(
|
|
4235
|
+
`}a(Og,"pageClientScriptPartB2CardsList");function $g(){return`
|
|
4166
4236
|
async function refreshFlowLabels() {
|
|
4167
4237
|
try {
|
|
4168
4238
|
var data = await api('/flows');
|
|
@@ -4187,7 +4257,7 @@ a{color:var(--focus)}
|
|
|
4187
4257
|
}
|
|
4188
4258
|
try {
|
|
4189
4259
|
await refreshFlowLabels();
|
|
4190
|
-
var pendingData = await api('/proposals?status=pending');
|
|
4260
|
+
var pendingData = await api('/proposals?status=pending,conflict');
|
|
4191
4261
|
// Refresh keeps however many decided cards were already loaded.
|
|
4192
4262
|
var decidedLimit = Math.min(200, Math.max(DECIDED_PAGE_SIZE, decidedLoaded));
|
|
4193
4263
|
var decidedData = await api('/proposals?status=' + DECIDED_STATUSES +
|
|
@@ -4294,7 +4364,7 @@ a{color:var(--focus)}
|
|
|
4294
4364
|
needsRerender = true;
|
|
4295
4365
|
}
|
|
4296
4366
|
var p = cachedProposals.filter(function (x) { return x.id === id; })[0];
|
|
4297
|
-
if (p && p.status !== 'pending' && proposalsFilter === 'pending') {
|
|
4367
|
+
if (p && (p.status !== 'pending' && p.status !== 'conflict') && proposalsFilter === 'pending') {
|
|
4298
4368
|
proposalsFilter = 'decided';
|
|
4299
4369
|
var filtersEl = $('proposals-filters');
|
|
4300
4370
|
if (filtersEl) {
|
|
@@ -4337,7 +4407,7 @@ a{color:var(--focus)}
|
|
|
4337
4407
|
replaceLocationHash('#proposals');
|
|
4338
4408
|
return true;
|
|
4339
4409
|
}
|
|
4340
|
-
`}a(
|
|
4410
|
+
`}a($g,"pageClientScriptPartB2Focus");function Mg(){return`
|
|
4341
4411
|
function bindDiffModeToggle(host, diff) {
|
|
4342
4412
|
host.querySelectorAll('[data-diff-mode]').forEach(function (btn) {
|
|
4343
4413
|
btn.addEventListener('click', function () {
|
|
@@ -4352,14 +4422,18 @@ a{color:var(--focus)}
|
|
|
4352
4422
|
}
|
|
4353
4423
|
|
|
4354
4424
|
function applyDiffToProposalCard(id, diff) {
|
|
4355
|
-
proposalDiffCache[id] = diff;
|
|
4356
4425
|
var p = cachedProposals.filter(function (x) { return x.id === id; })[0];
|
|
4357
4426
|
var wasStale = p ? proposalIsStale(p) : false;
|
|
4427
|
+
proposalDiffCache[id] = diff;
|
|
4428
|
+
if (p && diff.rebase && (p.status === 'pending' || p.status === 'conflict')) {
|
|
4429
|
+
p.rebase = Object.assign({}, p.rebase || {}, diff.rebase);
|
|
4430
|
+
p.status = diff.rebase.state === 'conflict' ? 'conflict' : 'pending';
|
|
4431
|
+
}
|
|
4358
4432
|
if (p && typeof diff.baseRevStale === 'boolean') {
|
|
4359
4433
|
p.baseRevStale = diff.baseRevStale;
|
|
4360
4434
|
}
|
|
4361
|
-
//
|
|
4362
|
-
if (p && p.status === 'pending' &&
|
|
4435
|
+
// Move cards between review and conflict groups when the rebase state changes.
|
|
4436
|
+
if (p && (p.status === 'pending' || p.status === 'conflict') && wasStale !== proposalIsStale(p)) {
|
|
4363
4437
|
renderProposalsList();
|
|
4364
4438
|
return;
|
|
4365
4439
|
}
|
|
@@ -4381,7 +4455,19 @@ a{color:var(--focus)}
|
|
|
4381
4455
|
updatePendingBadges();
|
|
4382
4456
|
return;
|
|
4383
4457
|
}
|
|
4384
|
-
if (
|
|
4458
|
+
if (p) {
|
|
4459
|
+
article.setAttribute('data-status', p.status);
|
|
4460
|
+
var pill = article.querySelector('.status-pill');
|
|
4461
|
+
if (pill) { pill.className = 'status-pill ' + p.status; pill.textContent = p.status === 'conflict' ? 'Conflict' : p.status; }
|
|
4462
|
+
var conflictHost = article.querySelector('.proposal-conflict-host');
|
|
4463
|
+
if (conflictHost) conflictHost.innerHTML = renderConflictPanel(p, diff);
|
|
4464
|
+
var approveBtn = article.querySelector('.approve-btn');
|
|
4465
|
+
if (approveBtn) { approveBtn.disabled = proposalIsStale(p); approveBtn.title = approveBtn.disabled ? 'Resolve conflicts before approving' : ''; }
|
|
4466
|
+
var chip = article.querySelector('.rebase-chip');
|
|
4467
|
+
if (chip) chip.outerHTML = renderRebaseChip(p, diff);
|
|
4468
|
+
bindProposalActions(article);
|
|
4469
|
+
}
|
|
4470
|
+
if (p && proposalIsStale(p)) article.setAttribute('data-stale', '1');
|
|
4385
4471
|
else article.removeAttribute('data-stale');
|
|
4386
4472
|
|
|
4387
4473
|
// Banner for any large delete (stale or live) \u2014 owners must see the risk.
|
|
@@ -4411,7 +4497,7 @@ a{color:var(--focus)}
|
|
|
4411
4497
|
|
|
4412
4498
|
// Refresh headings from the live diff; rebuild disambiguation across peers
|
|
4413
4499
|
// (includes stale cards \u2014 relative "12h ago" alone must not be the only cue).
|
|
4414
|
-
var pendingPeers = cachedProposals.filter(function (x) { return x.status === 'pending'; });
|
|
4500
|
+
var pendingPeers = cachedProposals.filter(function (x) { return (x.status === 'pending' || x.status === 'conflict'); });
|
|
4415
4501
|
disambiguatedTitleCache = buildDisambiguatedTitles(pendingPeers);
|
|
4416
4502
|
pendingPeers.forEach(function (peer) {
|
|
4417
4503
|
var el = document.querySelector('[data-title-for="' + peer.id + '"]');
|
|
@@ -4449,7 +4535,7 @@ a{color:var(--focus)}
|
|
|
4449
4535
|
esc(e.message || String(e)) + '</p>';
|
|
4450
4536
|
}
|
|
4451
4537
|
}
|
|
4452
|
-
}`}a(
|
|
4538
|
+
}`}a(Mg,"pageClientScriptPartB2Apply");function Dg(){return`
|
|
4453
4539
|
|
|
4454
4540
|
function dialogFocusables(dialog) {
|
|
4455
4541
|
if (!dialog) return [];
|
|
@@ -4627,7 +4713,7 @@ a{color:var(--focus)}
|
|
|
4627
4713
|
renderProposalsList();
|
|
4628
4714
|
return;
|
|
4629
4715
|
}
|
|
4630
|
-
if (status && status !== 'pending' && proposalsFilter === 'pending') {
|
|
4716
|
+
if (status && status !== 'pending' && status !== 'conflict' && proposalsFilter === 'pending') {
|
|
4631
4717
|
var decidedMatches = countSearchMatchesInFilter('decided');
|
|
4632
4718
|
if (decidedMatches > 0) {
|
|
4633
4719
|
setProposalsFilter('decided');
|
|
@@ -4668,17 +4754,17 @@ a{color:var(--focus)}
|
|
|
4668
4754
|
var expectedRev = pBefore && pBefore.expectedRev;
|
|
4669
4755
|
var peersBefore = expectedRev
|
|
4670
4756
|
? cachedProposals.filter(function (x) {
|
|
4671
|
-
return x.id !== id && x.status === 'pending' && x.expectedRev === expectedRev;
|
|
4757
|
+
return x.id !== id && (x.status === 'pending' || x.status === 'conflict') && x.expectedRev === expectedRev;
|
|
4672
4758
|
}).length
|
|
4673
4759
|
: 0;
|
|
4674
4760
|
try {
|
|
4675
4761
|
var result = await api('/proposals/' + encodeURIComponent(id) + '/approve', {
|
|
4676
4762
|
method: 'POST',
|
|
4677
|
-
body: JSON.stringify({ deploymentType: deploymentType })
|
|
4763
|
+
body: JSON.stringify({ deploymentType: deploymentType, mergedHash: proposalDiffCache[id] && proposalDiffCache[id].rebase && proposalDiffCache[id].rebase.mergedHash })
|
|
4678
4764
|
});
|
|
4679
4765
|
var viewOpts = { title: toastTitle, href: proposalViewHref(id), linkLabel: 'View' };
|
|
4680
4766
|
if (result.status === 'rejected') {
|
|
4681
|
-
var rejectMsg =
|
|
4767
|
+
var rejectMsg = result.reason || 'Proposal rejected.';
|
|
4682
4768
|
showFlash(flash, rejectMsg, false);
|
|
4683
4769
|
showToast(rejectMsg, false, viewOpts);
|
|
4684
4770
|
} else if (result.status === 'failed') {
|
|
@@ -4692,9 +4778,9 @@ a{color:var(--focus)}
|
|
|
4692
4778
|
showToast('Approved & deployed.', true, viewOpts);
|
|
4693
4779
|
}
|
|
4694
4780
|
delete proposalDiffCache[id];
|
|
4695
|
-
//
|
|
4781
|
+
// Re-check peer diffs against the newly deployed flow.
|
|
4696
4782
|
cachedProposals.forEach(function (peer) {
|
|
4697
|
-
if (peer.id !== id && peer.status === 'pending' && peer.expectedRev === expectedRev) {
|
|
4783
|
+
if (peer.id !== id && (peer.status === 'pending' || peer.status === 'conflict') && peer.expectedRev === expectedRev) {
|
|
4698
4784
|
delete proposalDiffCache[peer.id];
|
|
4699
4785
|
peer.baseRevStale = true;
|
|
4700
4786
|
}
|
|
@@ -4704,8 +4790,8 @@ a{color:var(--focus)}
|
|
|
4704
4790
|
notifySidebarPendingRefresh();
|
|
4705
4791
|
if (result.status !== 'rejected' && result.status !== 'failed' && peersBefore > 0) {
|
|
4706
4792
|
var note = peersBefore === 1
|
|
4707
|
-
? '1 other proposal
|
|
4708
|
-
: peersBefore + ' other proposals
|
|
4793
|
+
? '1 other proposal was rechecked'
|
|
4794
|
+
: peersBefore + ' other proposals were rechecked';
|
|
4709
4795
|
var priorOk = flash && !flash.hidden ? String(flash.textContent || '').trim() : '';
|
|
4710
4796
|
showFlash(flash, (priorOk ? priorOk + ' ' : '') + note + '.', true);
|
|
4711
4797
|
showToast(note + '.', true);
|
|
@@ -4713,7 +4799,18 @@ a{color:var(--focus)}
|
|
|
4713
4799
|
settleSearchAfterAction(id, 'approved');
|
|
4714
4800
|
loadLogs();
|
|
4715
4801
|
} catch (e) {
|
|
4716
|
-
var
|
|
4802
|
+
var code = e.body && e.body.code;
|
|
4803
|
+
var errMsg = e.message || String(e);
|
|
4804
|
+
if (e.status === 409 && (code === 'rebase_ack_required' || code === 'proposal_conflict')) {
|
|
4805
|
+
errMsg = code === 'rebase_ack_required'
|
|
4806
|
+
? 'This proposal was rebased onto newer editor changes since you viewed it \u2014 review the updated diff'
|
|
4807
|
+
: 'Resolve the conflicts before approving this proposal.';
|
|
4808
|
+
delete proposalDiffCache[id];
|
|
4809
|
+
await loadProposals();
|
|
4810
|
+
await loadProposalDiff(id);
|
|
4811
|
+
} else if (e.status === 409 && code === 'rebase_race') {
|
|
4812
|
+
errMsg = 'Flows kept changing, try again';
|
|
4813
|
+
}
|
|
4717
4814
|
showFlash(flash, errMsg, false);
|
|
4718
4815
|
showToast(errMsg, false, toastTitle ? { title: toastTitle } : undefined);
|
|
4719
4816
|
}
|
|
@@ -4724,8 +4821,13 @@ a{color:var(--focus)}
|
|
|
4724
4821
|
var deploymentType = select ? select.value : defaultDeploy;
|
|
4725
4822
|
var diff = proposalDiffCache[id];
|
|
4726
4823
|
var p = cachedProposals.filter(function (x) { return x.id === id; })[0];
|
|
4727
|
-
if (diff && diff.
|
|
4728
|
-
showToast('
|
|
4824
|
+
if ((p && p.status === 'conflict') || (diff && diff.rebase && diff.rebase.state === 'conflict')) {
|
|
4825
|
+
showToast('Resolve the conflicts before approving this proposal.', false);
|
|
4826
|
+
return;
|
|
4827
|
+
}
|
|
4828
|
+
if (!diff) {
|
|
4829
|
+
await loadProposalDiff(id);
|
|
4830
|
+
showToast('Review the loaded diff, then click Approve again.', false);
|
|
4729
4831
|
return;
|
|
4730
4832
|
}
|
|
4731
4833
|
if (needsApproveConfirm(diff, p)) {
|
|
@@ -4781,7 +4883,7 @@ a{color:var(--focus)}
|
|
|
4781
4883
|
throw e;
|
|
4782
4884
|
}
|
|
4783
4885
|
}
|
|
4784
|
-
`}a(
|
|
4886
|
+
`}a(Dg,"pageClientScriptPartC");function Hg(){return`
|
|
4785
4887
|
var cachedLogs = [];
|
|
4786
4888
|
|
|
4787
4889
|
function renderLogsTable(logs) {
|
|
@@ -4863,7 +4965,7 @@ a{color:var(--focus)}
|
|
|
4863
4965
|
showFlash(flash, e.message || String(e), false);
|
|
4864
4966
|
}
|
|
4865
4967
|
}
|
|
4866
|
-
`}a(
|
|
4968
|
+
`}a(Hg,"pageClientScriptPartCLogs");function jg(){return`
|
|
4867
4969
|
function applyStatus(s) {
|
|
4868
4970
|
aiOn = !!s.aiFeaturesEnabled;
|
|
4869
4971
|
canManage = !!(s.capabilities && s.capabilities.manageLinks);
|
|
@@ -4926,7 +5028,7 @@ a{color:var(--focus)}
|
|
|
4926
5028
|
}
|
|
4927
5029
|
|
|
4928
5030
|
$('proposals-hint').textContent = canApprove
|
|
4929
|
-
? 'Review changes from your AI assistant. Each card shows a short id (first 8 hex after prop-; click to copy the full id). Approve & deploy makes them live; Reject leaves the flow unchanged
|
|
5031
|
+
? 'Review changes from your AI assistant. Each card shows a short id (first 8 hex after prop-; click to copy the full id). Approve & deploy makes them live; Reject leaves the flow unchanged. Resolve conflicts to review the merged change.'
|
|
4930
5032
|
: 'Approve, reject, and dismiss require full admin permission (*). You can still review proposals. Short ids are the first 8 hex characters after prop- (click to copy the full id).';
|
|
4931
5033
|
|
|
4932
5034
|
renderLinksManage();
|
|
@@ -5024,7 +5126,7 @@ a{color:var(--focus)}
|
|
|
5024
5126
|
});
|
|
5025
5127
|
}
|
|
5026
5128
|
}
|
|
5027
|
-
`}a(
|
|
5129
|
+
`}a(jg,"pageClientScriptPartD");function Ug(){return`
|
|
5028
5130
|
async function boot() {
|
|
5029
5131
|
accessToken = readEditorToken();
|
|
5030
5132
|
if (!accessToken) {
|
|
@@ -5128,11 +5230,11 @@ a{color:var(--focus)}
|
|
|
5128
5230
|
loadTabData(tab);
|
|
5129
5231
|
});
|
|
5130
5232
|
|
|
5131
|
-
${
|
|
5132
|
-
${
|
|
5233
|
+
${Bg()}
|
|
5234
|
+
${qg()}
|
|
5133
5235
|
boot();
|
|
5134
5236
|
})();
|
|
5135
|
-
`}a(
|
|
5237
|
+
`}a(Ug,"pageClientScriptPartDBoot");function Bg(){return`
|
|
5136
5238
|
/*
|
|
5137
5239
|
* Jump links to the editor (#flow/<id>, #node/<id>). A click first asks an
|
|
5138
5240
|
* already-open editor tab (BroadcastChannel 'node-red-ai-access', answered by
|
|
@@ -5197,7 +5299,7 @@ ${lh()}
|
|
|
5197
5299
|
ev.stopPropagation();
|
|
5198
5300
|
revealInEditor(a.getAttribute('data-reveal-kind'), a.getAttribute('data-reveal-id'), a.getAttribute('href'));
|
|
5199
5301
|
});
|
|
5200
|
-
`}a(
|
|
5302
|
+
`}a(Bg,"pageClientEditorJumpScript");function qg(){return`
|
|
5201
5303
|
/*
|
|
5202
5304
|
* Auto-refresh when a tab left open becomes visible / focused again.
|
|
5203
5305
|
* Throttled; skipped while a dialog (e.g. the one-time token modal) is open or
|
|
@@ -5247,7 +5349,7 @@ ${lh()}
|
|
|
5247
5349
|
});
|
|
5248
5350
|
window.addEventListener('focus', autoRefresh);
|
|
5249
5351
|
|
|
5250
|
-
`}a(
|
|
5352
|
+
`}a(qg,"pageClientAutoRefreshScript");function pc(){return(kg()+Ag()+Sg()+xg()+Tg()+Rg()+Pg()+_g()+Eg()+Cg()+Ig()+Lg()+Ng()+Fg()+Og()+$g()+Mg()+Dg()+Hg()+jg()+uc()+Ug()).trim()}a(pc,"pageClientScript");function zg(){return`
|
|
5251
5353
|
:root {
|
|
5252
5354
|
color-scheme: light;
|
|
5253
5355
|
--bg: #eef2f5;
|
|
@@ -5304,20 +5406,45 @@ h1 {
|
|
|
5304
5406
|
}
|
|
5305
5407
|
.how-it-works li { margin: 0.2rem 0; }
|
|
5306
5408
|
.how-it-works strong { color: var(--accent); }
|
|
5409
|
+
/* Signed-out card: same look as the OAuth consent / sign-in page (light only). */
|
|
5307
5410
|
.auth-gate {
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
.
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
}
|
|
5320
|
-
|
|
5411
|
+
--sg-bg: #f3f4f6; --sg-card: #fff; --sg-text: #111827; --sg-muted: #4b5563; --sg-line: #e5e7eb;
|
|
5412
|
+
--sg-soft: #f9fafb; --sg-brand: #8f0000; --sg-brand-2: #b91c1c; --sg-focus: #2563eb;
|
|
5413
|
+
color-scheme: light;
|
|
5414
|
+
position: fixed; inset: 0; z-index: 1000; overflow: auto;
|
|
5415
|
+
display: flex; align-items: center; justify-content: center; padding: 24px 16px;
|
|
5416
|
+
background: var(--sg-bg); color: var(--sg-text);
|
|
5417
|
+
font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
5418
|
+
}
|
|
5419
|
+
.auth-gate[hidden] { display: none !important; }
|
|
5420
|
+
body.signed-out { overflow: hidden; }
|
|
5421
|
+
.auth-gate * { box-sizing: border-box; }
|
|
5422
|
+
.auth-gate .sg-card { width: 100%; max-width: 440px; margin: 0; background: var(--sg-card); border: 1px solid var(--sg-line); border-radius: 16px; box-shadow: 0 10px 30px rgba(0,0,0,.08); overflow: hidden; }
|
|
5423
|
+
.auth-gate .sg-head { display: flex; align-items: center; gap: 12px; padding: 18px 22px; border-bottom: 1px solid var(--sg-line); }
|
|
5424
|
+
.auth-gate .logo { width: 40px; height: 40px; flex: none; }
|
|
5425
|
+
.auth-gate .sg-brand b { display: block; font-size: 15px; }
|
|
5426
|
+
.auth-gate .sg-brand span { font-size: 13px; color: var(--sg-muted); }
|
|
5427
|
+
.auth-gate .sg-body { padding: 20px 22px 22px; }
|
|
5428
|
+
.auth-gate h1 { font-size: 19px; line-height: 1.3; margin: 0 0 6px; color: var(--sg-text); }
|
|
5429
|
+
.auth-gate .sg-sub { margin: 0 0 14px; color: var(--sg-muted); font-size: 14px; }
|
|
5430
|
+
.auth-gate .sg-status { font-size: 14px; color: var(--sg-muted); margin: 0 0 14px; }
|
|
5431
|
+
.auth-gate .sg-status.error { color: var(--sg-brand-2); }
|
|
5432
|
+
.auth-gate .sg-actions { display: flex; gap: 10px; }
|
|
5433
|
+
.auth-gate button { font: inherit; border-radius: 10px; padding: 10px 16px; cursor: pointer; border: 1px solid var(--sg-line); background: var(--sg-card); color: var(--sg-text); }
|
|
5434
|
+
.auth-gate button:focus-visible, .auth-gate input:focus-visible, .auth-gate a:focus-visible { outline: 3px solid var(--sg-focus); outline-offset: 2px; }
|
|
5435
|
+
.auth-gate button[disabled] { opacity: .6; cursor: default; }
|
|
5436
|
+
.auth-gate .sg-primary { flex: 1; background: var(--sg-brand); border-color: var(--sg-brand); color: #fff; font-weight: 600; }
|
|
5437
|
+
.auth-gate .sg-primary:hover:not([disabled]) { background: var(--sg-brand-2); }
|
|
5438
|
+
.auth-gate .sg-secondary { flex: none; }
|
|
5439
|
+
.auth-gate .sg-login { display: grid; gap: 10px; margin: 0 0 4px; }
|
|
5440
|
+
.auth-gate .sg-login label { display: grid; gap: 4px; font-size: 13px; color: var(--sg-muted); }
|
|
5441
|
+
.auth-gate .sg-login input { font: inherit; padding: 9px 11px; border-radius: 10px; border: 1px solid var(--sg-line); background: var(--sg-soft); color: var(--sg-text); }
|
|
5442
|
+
.auth-gate .sg-editor-option { display: inline-block; margin: 10px 0 14px; font-size: 14px; }
|
|
5443
|
+
.auth-gate .sg-editor-option[hidden] { display: none; }
|
|
5444
|
+
.auth-gate .sg-foot { padding: 12px 22px; border-top: 1px solid var(--sg-line); font-size: 12px; color: var(--sg-muted); background: var(--sg-soft); }
|
|
5445
|
+
.auth-gate a { color: var(--sg-focus); }
|
|
5446
|
+
@media (max-width: 420px) { .auth-gate .sg-actions { flex-direction: column-reverse; } .auth-gate .sg-secondary { flex: 1; } }
|
|
5447
|
+
`}a(zg,"pageStylesPartA");function Wg(){return`
|
|
5321
5448
|
nav.tabs {
|
|
5322
5449
|
position: sticky;
|
|
5323
5450
|
top: 0;
|
|
@@ -5411,7 +5538,7 @@ dd {
|
|
|
5411
5538
|
padding: 0.22rem 0.55rem;
|
|
5412
5539
|
border-radius: 6px;
|
|
5413
5540
|
cursor: help;
|
|
5414
|
-
background: var`}a(
|
|
5541
|
+
background: var`}a(Wg,"pageStylesPartA2");function Gg(){return`(--accent-soft);
|
|
5415
5542
|
color: var(--accent);
|
|
5416
5543
|
font-size: 0.82rem;
|
|
5417
5544
|
font-weight: 650;
|
|
@@ -5602,7 +5729,7 @@ dialog.token-modal { width: min(720px, 94vw); max-width: min(720px, 94vw); max-h
|
|
|
5602
5729
|
color: var(--muted);
|
|
5603
5730
|
font-size: 0.92rem;
|
|
5604
5731
|
}
|
|
5605
|
-
`}a(
|
|
5732
|
+
`}a(Gg,"pageStylesPartB");function Kg(){return`
|
|
5606
5733
|
.logs-toolbar { margin-bottom: 0.65rem; }
|
|
5607
5734
|
table.audit-table {
|
|
5608
5735
|
table-layout: fixed;
|
|
@@ -5678,7 +5805,7 @@ table.audit-table {
|
|
|
5678
5805
|
cursor: help;
|
|
5679
5806
|
border-bottom: 1px dotted var(--line);
|
|
5680
5807
|
}
|
|
5681
|
-
`}a(
|
|
5808
|
+
`}a(Kg,"pageStylesPartC");function Jg(){return`
|
|
5682
5809
|
.proposal-diff {
|
|
5683
5810
|
margin: 0.65rem 0;
|
|
5684
5811
|
padding: 0.55rem 0.65rem;
|
|
@@ -5716,6 +5843,10 @@ table.audit-table {
|
|
|
5716
5843
|
color: #fff;
|
|
5717
5844
|
}
|
|
5718
5845
|
.diff-summary { margin: 0 0 0.35rem; }
|
|
5846
|
+
.status-pill.conflict { background: #fff1df; color: #9a4700; border: 1px solid #edbd80; }
|
|
5847
|
+
.rebase-chip { font-size: 0.8rem; color: #245d46; background: #eaf5ee; border-radius: 4px; padding: 0.2rem 0.4rem; }
|
|
5848
|
+
.proposal-conflicts { background: #fff8ee; border: 1px solid #edbd80; border-radius: 6px; padding: 1rem; margin: 0.75rem 0; overflow-wrap: anywhere; }
|
|
5849
|
+
.proposal-conflict { border-bottom: 1px solid #edbd80; padding-bottom: 0.75rem; margin-bottom: 0.75rem; }
|
|
5719
5850
|
.stale-banner {
|
|
5720
5851
|
margin: 0 0 0.55rem;
|
|
5721
5852
|
padding: 0.45rem 0.6rem;
|
|
@@ -5755,7 +5886,7 @@ table.audit-table {
|
|
|
5755
5886
|
color: var(--muted);
|
|
5756
5887
|
margin-bottom: 0.15rem;
|
|
5757
5888
|
}
|
|
5758
|
-
`}a(
|
|
5889
|
+
`}a(Jg,"pageStylesPartD");function Vg(){return`
|
|
5759
5890
|
.diff-block {
|
|
5760
5891
|
margin-top: 0.25rem;
|
|
5761
5892
|
font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
|
|
@@ -5837,7 +5968,7 @@ table.audit-table {
|
|
|
5837
5968
|
}
|
|
5838
5969
|
.diff-before { color: var(--danger); word-break: break-word; }
|
|
5839
5970
|
.diff-after { color: var(--ok); word-break: break-word; }
|
|
5840
|
-
`}a(
|
|
5971
|
+
`}a(Vg,"pageStylesPartE");function Yg(){return`
|
|
5841
5972
|
.proposal-detail {
|
|
5842
5973
|
scroll-margin-top: 4.5rem;
|
|
5843
5974
|
}
|
|
@@ -5963,7 +6094,7 @@ table.audit-table {
|
|
|
5963
6094
|
.audit-table .col-action { width: 5.5rem; min-width: 5.5rem; }
|
|
5964
6095
|
.audit-table .col-actor { width: 7rem; min-width: 6rem; }
|
|
5965
6096
|
}
|
|
5966
|
-
`}a(
|
|
6097
|
+
`}a(Yg,"pageStylesPartF");function Xg(){return`
|
|
5967
6098
|
.dash-grid {
|
|
5968
6099
|
display: grid;
|
|
5969
6100
|
grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
|
|
@@ -6195,7 +6326,7 @@ button.id-copy:focus {
|
|
|
6195
6326
|
background: var(--code-bg, var(--soft));
|
|
6196
6327
|
border-radius: 4px;
|
|
6197
6328
|
}
|
|
6198
|
-
`}a(
|
|
6329
|
+
`}a(Xg,"pageStylesPartG");function Zg(){return`
|
|
6199
6330
|
.stale-group {
|
|
6200
6331
|
margin: 1rem 0 0;
|
|
6201
6332
|
border: 1px solid var(--warn-border);
|
|
@@ -6359,7 +6490,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
6359
6490
|
margin-left: auto;
|
|
6360
6491
|
}
|
|
6361
6492
|
.debug-toggle input { width: auto; }
|
|
6362
|
-
`}a(
|
|
6493
|
+
`}a(Zg,"pageStylesPartH");function Qg(){return`
|
|
6363
6494
|
.card {
|
|
6364
6495
|
background: var(--card);
|
|
6365
6496
|
border: 1px solid var(--line);
|
|
@@ -6429,7 +6560,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
6429
6560
|
nav.tabs button { flex: 0 0 auto; }
|
|
6430
6561
|
.field-line { flex-wrap: wrap; }
|
|
6431
6562
|
}
|
|
6432
|
-
`}a(
|
|
6563
|
+
`}a(Qg,"pageStylesDashboard");function fc(){return(zg()+Wg()+Gg()+Kg()+Jg()+Vg()+Yg()+Xg()+Zg()+Qg()).trim()}a(fc,"pageStyles");function ey(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""")}a(ey,"escapeHtml");function ty(){return`
|
|
6433
6564
|
<header class="page-header">
|
|
6434
6565
|
<h1>Node-RED AI Access</h1>
|
|
6435
6566
|
<p class="lede">
|
|
@@ -6443,12 +6574,25 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
6443
6574
|
</header>
|
|
6444
6575
|
<div id="boot-flash" class="flash" hidden></div>
|
|
6445
6576
|
<div id="auth-gate" class="auth-gate" hidden>
|
|
6446
|
-
<
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6577
|
+
<div class="sg-card" role="dialog" aria-modal="true" aria-labelledby="auth-gate-title">
|
|
6578
|
+
<div class="sg-head">${Fi}<div class="sg-brand"><b>Node-RED AI Access</b><span>Dashboard</span></div></div>
|
|
6579
|
+
<div class="sg-body">
|
|
6580
|
+
<h1 id="auth-gate-title">Sign in to Node-RED</h1>
|
|
6581
|
+
<p class="sg-sub">AI Access uses your Node-RED editor account. Signing in here also signs in the editor in this browser.</p>
|
|
6582
|
+
<p id="auth-gate-status" class="sg-status" role="status" aria-live="polite">Checking how Node-RED signs you in\u2026</p>
|
|
6583
|
+
<form id="auth-gate-login" method="post" class="sg-login" hidden autocomplete="on">
|
|
6584
|
+
<label>Username<input id="auth-gate-username" name="username" autocomplete="username" required></label>
|
|
6585
|
+
<label>Password<input id="auth-gate-password" name="password" type="password" autocomplete="current-password" required></label>
|
|
6586
|
+
<div class="sg-actions"><button id="auth-gate-submit" class="sg-primary" type="submit">Sign in</button></div>
|
|
6587
|
+
</form>
|
|
6588
|
+
<a id="auth-gate-editor-link" class="sg-editor-option" target="node-red-editor" rel="noopener" hidden>or sign in via the editor</a>
|
|
6589
|
+
<div class="sg-actions">
|
|
6590
|
+
<button id="auth-gate-editor" class="sg-primary" type="button" hidden>Sign in with the Node-RED editor</button>
|
|
6591
|
+
<button id="auth-gate-retry" class="sg-secondary" type="button" hidden>Retry</button>
|
|
6592
|
+
</div>
|
|
6593
|
+
</div>
|
|
6594
|
+
<div class="sg-foot">You only need to sign in once \u2014 the dashboard continues automatically.</div>
|
|
6595
|
+
</div>
|
|
6452
6596
|
</div>
|
|
6453
6597
|
<div id="toast-host" class="toast-host" aria-live="polite"></div>
|
|
6454
6598
|
<nav class="tabs" aria-label="Admin views" hidden id="main-nav">
|
|
@@ -6460,7 +6604,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
6460
6604
|
Proposals <span class="tab-badge" id="proposals-pending-badge" hidden>0</span>
|
|
6461
6605
|
</button>
|
|
6462
6606
|
<button type="button" data-tab="logs" aria-selected="false">Audit log</button>
|
|
6463
|
-
</nav>`}a(
|
|
6607
|
+
</nav>`}a(ty,"renderMcpPageHeader");function ny(){return`
|
|
6464
6608
|
<section class="panel active" id="section-status" aria-label="Overview" hidden>
|
|
6465
6609
|
<h2>Overview</h2>
|
|
6466
6610
|
<div id="status-flash" class="flash" hidden></div>
|
|
@@ -6504,7 +6648,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
6504
6648
|
<div class="row" style="margin-top:1rem">
|
|
6505
6649
|
<button type="button" class="action secondary" id="refresh-status">Refresh</button>
|
|
6506
6650
|
</div>
|
|
6507
|
-
</section>`}a(
|
|
6651
|
+
</section>`}a(ny,"renderMcpPageStatusSection");function ry(){return`
|
|
6508
6652
|
<section class="panel" id="section-links" aria-label="Access tokens" hidden>
|
|
6509
6653
|
<h2>Access tokens</h2>
|
|
6510
6654
|
<p class="muted" id="links-intro">
|
|
@@ -6515,7 +6659,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
6515
6659
|
<div id="links-flash" class="flash" hidden></div>
|
|
6516
6660
|
<div id="links-manage"></div>
|
|
6517
6661
|
<div id="links-list"></div>
|
|
6518
|
-
</section>`}a(
|
|
6662
|
+
</section>`}a(ry,"renderMcpPageLinksSection");function be(e){return`<span class="lock-badge" id="lock-${e}" hidden>Locked by settings.js</span>`}a(be,"lockBadge");function oy(){return`
|
|
6519
6663
|
<section class="panel" id="section-settings" aria-label="Settings" hidden>
|
|
6520
6664
|
<h2>Settings</h2>
|
|
6521
6665
|
<p class="muted">Changes save automatically and apply immediately (no restart).</p>
|
|
@@ -6556,6 +6700,14 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
6556
6700
|
${be("ttl")}
|
|
6557
6701
|
</div>
|
|
6558
6702
|
</div>
|
|
6703
|
+
<div class="form-row">
|
|
6704
|
+
<label for="set-proposal-ttl">Proposal expiry (days)</label>
|
|
6705
|
+
<div class="field-line">
|
|
6706
|
+
<input type="number" id="set-proposal-ttl" min="1" max="365" step="1" value="7" class="num" />
|
|
6707
|
+
${be("proposal-ttl")}
|
|
6708
|
+
</div>
|
|
6709
|
+
<p class="hint">Open proposals expire after this many days from submission (1\u2013365, default 7).</p>
|
|
6710
|
+
</div>
|
|
6559
6711
|
<div class="form-row">
|
|
6560
6712
|
<label for="set-max-links">Maximum active tokens</label>
|
|
6561
6713
|
<div class="field-line">
|
|
@@ -6642,7 +6794,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
6642
6794
|
<p class="hint" id="set-trust-proxy-inline"></p>
|
|
6643
6795
|
</div>
|
|
6644
6796
|
</div>
|
|
6645
|
-
</section>`}a(
|
|
6797
|
+
</section>`}a(oy,"renderMcpPageSettingsSection");function sy(){return`
|
|
6646
6798
|
<section class="panel" id="section-oauth" aria-label="OAuth" hidden>
|
|
6647
6799
|
<h2>MCP OAuth</h2>
|
|
6648
6800
|
<p class="muted">MCP clients (ChatGPT, Claude, \u2026) can request read or propose access through OAuth.</p>
|
|
@@ -6717,7 +6869,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
6717
6869
|
<button type="button" class="action secondary" id="oauth-refresh">Refresh</button></div>
|
|
6718
6870
|
<div id="oauth-clients" aria-live="polite"><p class="muted">Loading\u2026</p></div>
|
|
6719
6871
|
</div>
|
|
6720
|
-
</section>`}a(
|
|
6872
|
+
</section>`}a(sy,"renderMcpPageOAuthSection");function iy(){return`
|
|
6721
6873
|
<section class="panel" id="section-proposals" aria-label="Proposals" hidden>
|
|
6722
6874
|
<h2>Proposals</h2>
|
|
6723
6875
|
<p id="proposals-hint" class="muted"></p>
|
|
@@ -6735,7 +6887,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
6735
6887
|
</div>
|
|
6736
6888
|
<div id="proposals-flash" class="flash" hidden></div>
|
|
6737
6889
|
<div id="proposals-list"><p class="muted">Loading\u2026</p></div>
|
|
6738
|
-
</section>`}a(
|
|
6890
|
+
</section>`}a(iy,"renderMcpPageProposalsSection");function ay(){return`
|
|
6739
6891
|
<section class="panel" id="section-logs" aria-label="Audit log" hidden>
|
|
6740
6892
|
<h2>Audit log</h2>
|
|
6741
6893
|
<div id="logs-flash" class="flash" hidden></div>
|
|
@@ -6751,7 +6903,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
6751
6903
|
</label>
|
|
6752
6904
|
</div>
|
|
6753
6905
|
<div id="logs-list"><p class="muted">Loading\u2026</p></div>
|
|
6754
|
-
</section>`}a(
|
|
6906
|
+
</section>`}a(ay,"renderMcpPageLogsSection");function dy(){return`
|
|
6755
6907
|
<dialog id="reject-dialog">
|
|
6756
6908
|
<form method="dialog" id="reject-form">
|
|
6757
6909
|
<h3>Reject proposal</h3>
|
|
@@ -6793,7 +6945,7 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
6793
6945
|
<button type="submit" value="confirm" class="action danger" id="confirm-action-btn">Confirm</button>
|
|
6794
6946
|
</div>
|
|
6795
6947
|
</form>
|
|
6796
|
-
</dialog>`}a(
|
|
6948
|
+
</dialog>`}a(dy,"renderMcpPageDialogs");function hc(e){let t={apiBase:e.apiBase,mcpRpc:e.mcpRpc??"/mcp/rpc",dashboardPath:e.dashboardPath??"/ai-access",editorRoot:e.editorRoot,authTokenKey:e.authTokenKey};return`<!DOCTYPE html>
|
|
6797
6949
|
<html lang="en">
|
|
6798
6950
|
<head>
|
|
6799
6951
|
<meta charset="utf-8" />
|
|
@@ -6801,23 +6953,23 @@ dialog h3 { margin: 0 0 0.4rem; }
|
|
|
6801
6953
|
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" />
|
|
6802
6954
|
<meta http-equiv="Pragma" content="no-cache" />
|
|
6803
6955
|
<title>Node-RED AI Access</title>
|
|
6804
|
-
<style>${
|
|
6956
|
+
<style>${fc()}</style>
|
|
6805
6957
|
</head>
|
|
6806
6958
|
<body>
|
|
6807
6959
|
<main
|
|
6808
6960
|
id="mcp-root"
|
|
6809
|
-
data-config="${
|
|
6810
|
-
>${
|
|
6811
|
-
${
|
|
6812
|
-
${
|
|
6813
|
-
${
|
|
6814
|
-
${
|
|
6815
|
-
${
|
|
6816
|
-
${
|
|
6817
|
-
${
|
|
6961
|
+
data-config="${ey(JSON.stringify(t))}"
|
|
6962
|
+
>${ty()}
|
|
6963
|
+
${ny()}
|
|
6964
|
+
${oy()}
|
|
6965
|
+
${ry()}
|
|
6966
|
+
${sy()}
|
|
6967
|
+
${iy()}
|
|
6968
|
+
${ay()}
|
|
6969
|
+
${dy()}
|
|
6818
6970
|
</main>
|
|
6819
|
-
<script>${
|
|
6971
|
+
<script>${pc()}</script>
|
|
6820
6972
|
</body>
|
|
6821
|
-
</html>`}a(Bd,"renderMcpPage");Re();xe();Ze();G();pt();J();De();tt();Re();nt();function Nt(e,t,n,r={}){e.setHeader("X-Content-Type-Options","nosniff"),e.setHeader("Content-Type","application/json; charset=utf-8");let o={error:je[n]??"Request failed",code:n,...r},s=de(n,"rest");s&&typeof o.hint!="string"&&(o.hint=s),e.status(t).json(o)}a(Nt,"sendSafeJsonError");function qd(e){let t={kind:"admin"};return typeof e.user?.username=="string"&&(t.username=e.user.username),t}a(qd,"adminActor");function zd(e,t,n,r,o){return async(s,i)=>{if(!s.accessLink){i.setHeader("WWW-Authenticate",X),i.status(401).json({error:me,code:Ie});return}try{let d=q(s,r),l=ie(s.query),c=await rt(e,t,s.accessLink,s.body??{},n,d,o),u=V(e,c.expectedRev,{flowId:c.payload.flowId}),p=Oe(e,e.get(c.id),s.accessLink.id),f=c.warnings;i.status(201).json({...he(c,{...d,include:l}),mine:!0,...$e(p),...Z(u),...f&&f.length>0?{warnings:f}:{}})}catch(d){let l=M(d,"rest");if(l){i.status(l.statusCode).json(l.body);return}D(i,d);return}}}a(zd,"createProposalHandler");function Wd(e,t,n){return async(r,o)=>{let s,i;try{let T=await t.getFlows();s=T.rev,i=Rr(T.flows)}catch{}let d={...q(r,n),...i?{liveFlowLabels:i}:{}},l=ie(r.query),c;try{c=Mr(r.query)}catch(T){D(o,T);return}let u=s?V(e,s):0,p=r.accessLink;if(p){let T=Zt(e,p,d,c),C=T.proposals.map(L=>{let N=ge(e,p,L,{...d,include:l},{includeConflicts:!1});return N.status!=="pending"||s===void 0?N:{...N,baseRevStale:N.expectedRev!==s}});o.json({proposals:C,...kt({...T,returned:T.proposals.length}),...Z(u)});return}let f={};c.flowId&&(f.flowId=c.flowId);let m=Ti(e.list(f).filter(T=>ns(T.status,c.status)),{...d,include:l}),h=c.offset??0,k=(c.limit!==void 0?m.slice(h,h+c.limit):h>0?m.slice(h):m).map(T=>T.status!=="pending"||s===void 0?T:{...T,baseRevStale:T.expectedRev!==s});o.json({proposals:k,...kt({total:m.length,limit:c.limit??null,offset:h,returned:k.length}),...Z(u)})}}a(Wd,"listProposalsHandler");function Gd(e,t,n){return async(r,o)=>{let s=r.params.id;if(typeof s!="string"||s.length===0){o.status(400).json({error:"Missing proposal id"});return}let i=q(r,t),d=ie(r.query),l;if(n)try{l=(await n.getFlows()).rev}catch{}try{if(r.accessLink){let c=ze(e,r.accessLink,s,i);o.json(ge(e,r.accessLink,c,{...i,include:d},{...l?{liveRev:l}:{}}));return}l&&V(e,l),o.json(he(e.get(s),{...i,include:d}))}catch(c){if(c instanceof se){Nt(o,404,c.code);return}let u=M(c,"rest");if(u){o.status(u.statusCode).json(u.body);return}D(o,c);return}}}a(Gd,"getProposalHandler");function Kd(e,t){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){r.status(400).json({error:"Missing proposal id"});return}let s=n.body??{},i={};if(s.deploymentType!==void 0){if(s.deploymentType!=="nodes"&&s.deploymentType!=="flows"&&s.deploymentType!=="full"){r.status(400).json({error:"deploymentType must be 'nodes', 'flows', or 'full'"});return}i.deploymentType=s.deploymentType}try{let d=await e.approve(o,qd(n),i);r.json(ae(d,q(n,t)))}catch(d){if(d instanceof se||d instanceof ne||d instanceof In||d instanceof Y){Nt(r,d.statusCode,d.code);return}D(r,d);return}}}a(Kd,"approveProposalHandler");function Jd(e,t){return(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){r.status(400).json({error:"Missing proposal id"});return}let s=n.body??{},i=typeof s.reason=="string"?s.reason:"Rejected by admin";try{let d=ae(e.reject(o,qd(n),i),q(n,t));r.json(d)}catch(d){if(d instanceof se||d instanceof ne||d instanceof Y){Nt(r,d.statusCode,d.code);return}D(r,d);return}}}a(Jd,"rejectProposalHandler");function Vd(e,t,n){return(r,o)=>{if(!r.accessLink){o.setHeader("WWW-Authenticate",X),o.status(401).json({error:me,code:Ie});return}let s=r.params.id;if(typeof s!="string"||s.length===0){o.status(400).json({error:"Missing proposal id",code:"missing_proposal_id"});return}let i=r.body??{},d=q(r,n),l=ie(r.query);try{let c=Qt(e,t,r.accessLink,s,i.reason,d);o.json(ge(e,r.accessLink,c,{...d,include:l}))}catch(c){let u=M(c,"rest");if(u){Nt(o,u.statusCode,u.body.code);return}Nt(o,500,"internal_error");return}}}a(Vd,"withdrawProposalHandler");function Yd(e,t){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){r.status(400).json({error:"Missing proposal id"});return}try{let s=n.accessLink?ze(e,n.accessLink,o):e.get(o),i=sn(n.query.format);if(!i.ok){r.status(400).json({error:"Invalid diff format",code:"invalid_diff_format",hint:"Valid formats: summary, full (default summary)."});return}let d=an(await un(s,Me(n,t)),i.format);r.json(d)}catch(s){if(s instanceof se){Nt(r,404,s.code);return}if(s instanceof W||s instanceof Se||s instanceof Ne){Nt(r,s.statusCode,s.code);return}let i=M(s,"rest");if(i){r.status(i.statusCode).json(i.body);return}D(r,s);return}}}a(Yd,"getProposalDiffHandler");nt();xe();xe();Ar();function Eh(e){return e.endsWith("/")?e.slice(0,-1):e}a(Eh,"trimTrailingSlash");var mo=class{static{a(this,"HttpAdapter")}kind="http";baseUrl;accessToken;fetchImpl;constructor(t){this.baseUrl=Eh(t.baseUrl),this.accessToken=t.accessToken,this.fetchImpl=t.fetch??globalThis.fetch.bind(globalThis)}headers(t){let n=new Headers(t);return n.set("Accept","application/json"),n.set("Node-RED-API-Version","v2"),this.accessToken&&n.set("Authorization",`Bearer ${this.accessToken}`),n}async request(t,n={}){return await this.fetchImpl(`${this.baseUrl}${t}`,{...n,headers:this.headers(n.headers)})}async readJson(t,n){let r=await t.text();if(!t.ok){let o=r;try{let s=JSON.parse(r);o=s.message??s.code??r}catch{}throw t.status===404?new W(n):t.status===409?new K("unknown","unknown",`Admin API ${n}`):t.status===401||t.status===403?new Ne(hr):new Error(`Admin API ${n} failed (${t.status}): ${o}`)}if(!r)return null;try{return JSON.parse(r)}catch{throw new Error(`Admin API ${n} returned non-JSON`)}}async getFlows(){let t=await this.request("/flows"),n=await this.readJson(t,"GET /flows");return Nn(n,"GET /flows")}async getFlow(t){let n=encodeURIComponent(t),[r,o]=await Promise.all([this.request("/flows"),this.request(`/flow/${n}`)]);if(o.status===404)throw new W(t);let s=await this.readJson(r,"GET /flows"),i=await this.readJson(o,`GET /flow/${t}`);return{rev:Nn(s,"GET /flows").rev,flow:qt(t,i)}}async updateFlow(t){let n=t.deploymentType??"nodes",r=await this.getFlows();if(r.rev!==t.expectedRev)throw new K(t.expectedRev,r.rev,"updateFlow");let o=kr(r.flows,t.flowId,t.flow),s=await this.request("/flows",{method:"POST",headers:{"Content-Type":"application/json","Node-RED-Deployment-Type":n},body:JSON.stringify({flows:o,rev:t.expectedRev})});if(s.status===409){let d=r.rev;try{let l=await s.clone().json();typeof l.current_rev=="string"&&(d=l.current_rev)}catch{}throw new K(t.expectedRev,d,"updateFlow")}let i=await this.readJson(s,"POST /flows");if(!i||typeof i!="object"||typeof i.rev!="string")throw new Error("Admin API POST /flows did not return a rev");return{flowId:t.flowId,rev:i.rev}}};xe();Ar();function Ch(e){let t=e.runtime?.flows;if(!t?.getFlows||!t.getFlow||!t.setFlows)throw new Error("RED.runtime.flows is missing getFlows, getFlow, or setFlows");return t}a(Ch,"requireFlowsApi");function Lh(e){if(typeof e=="string")return e;if(typeof e.rev=="string"&&e.rev.length>0)return e.rev;throw new Error("RED.runtime.flows.setFlows did not return a rev")}a(Lh,"resolveRev");var ho=class{static{a(this,"RedAdapter")}kind="red";flows;constructor(t){this.flows=Ch(t)}async getFlows(){let t=await this.flows.getFlows({});return Nn(t,"RED.runtime.flows.getFlows()")}async getFlow(t){let n=await this.getFlows(),r;try{r=await this.flows.getFlow({id:t})}catch(o){throw Ih(o)?new W(t):o}return{rev:n.rev,flow:qt(t,r)}}async updateFlow(t){let n=t.deploymentType??"nodes",r=await this.getFlows();if(r.rev!==t.expectedRev)throw new K(t.expectedRev,r.rev,"updateFlow");let o=kr(r.flows,t.flowId,t.flow),s=await this.flows.setFlows({flows:{flows:o,rev:t.expectedRev},deploymentType:n});return{flowId:t.flowId,rev:Lh(s)}}};function Xd(e){let t=e?.runtime?.flows;return!!(t?.getFlows&&t.getFlow&&t.setFlows)}a(Xd,"canUseRedRuntime");function Ih(e){if(!e||typeof e!="object")return!1;let t=e;if((typeof t.status=="number"?t.status:t.statusCode)===404||t.code==="not_found"||t.code==="flow_not_found")return!0;let r=typeof t.message=="string"?t.message.toLowerCase():"";return r.includes("not found")||r.includes("unknown flow")}a(Ih,"isRuntimeFlowNotFound");var Zd=require("module");function Qd(e){if(!e||typeof e!="object")return!1;let t=e;return typeof t.getFlows=="function"&&typeof t.getFlow=="function"&&typeof t.setFlows=="function"}a(Qd,"isFlowsApi");function Nh(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,Zd.createRequire)(e)}a(Nh,"createPluginRequire");function Fh(e){try{let t=e("@node-red/runtime/lib/settings");return typeof t.available=="function"&&t.available()===!0}catch{return!1}}a(Fh,"isNodeRedRuntimeInitialized");function Oh(){try{let e=Nh();if(!Fh(e))return null;let t=e("@node-red/runtime");return Qd(t.flows)?t.flows:null}catch{return null}}a(Oh,"tryRequireInitializedRuntimeFlows");function el(e){if(e?.runtime&&Qd(e.runtime.flows))return e;let t=Oh();return t?{runtime:{flows:t}}:null}a(el,"resolveRuntimeHost");function tl(e={}){let t=el(e.RED??null);if(t&&Xd(t))return new ho(t);let n=e.adminAuthEnabled===!0;if(e.http?.baseUrl){if(n&&!e.http.accessToken)throw new Se("In-process Node-RED flow runtime is unavailable and HTTP Admin API fallback has no credentials while adminAuth is enabled. Ensure the plugin runs inside Node-RED so @node-red/runtime.flows can be used.");return new mo(e.http)}throw new Se("No usable Node-RED flow adapter: in-process flows API is unavailable and no HTTP Admin API baseUrl was provided")}a(tl,"createFlowAdapter");xe();var yo=require("fs"),nl=require("path");yr();G();var $h=/^[a-z][a-z0-9._-]{0,63}$/;function Mh(e){if(e.kind==="system")return{kind:"system"};if(e.kind==="access-link"){let n={kind:"access-link",linkId:String(e.linkId).replace(/\0/g,"").slice(0,200)};return typeof e.label=="string"&&(n.label=e.label.replace(/\0/g,"").slice(0,200)),n}let t={kind:"admin"};return typeof e.username=="string"&&(t.username=e.username.replace(/\0/g,"").slice(0,200)),t}a(Mh,"sanitizeActor");var go=class{constructor(t,n={}){this.paths=t;this.filePath=(0,nl.join)(t.logs,"audit.jsonl"),this.now=n.now??(()=>Date.now()),this.maxAppendBytes=n.maxAppendBytes??Ms}static{a(this,"AuditLog")}filePath;now;maxAppendBytes;seq=0;append(t,n,r){let o=$h.test(t)?t:"unknown";this.seq+=1;let s={id:`log-${this.now()}-${this.seq}`,at:new Date(this.now()).toISOString(),action:o,actor:Mh(n)};r!==void 0&&(s.details=ni(r));let i=Je(s,this.maxAppendBytes,{kind:"audit"});return js(this.paths.root,this.filePath,i),s}list(t=100){if(!(0,yo.existsSync)(this.filePath))return[];let n=(0,yo.readFileSync)(this.filePath,"utf8").split(`
|
|
6822
|
-
`).map(o=>o.trim()).filter(Boolean),r=[];for(let o of n)try{r.push(JSON.parse(o))}catch{}return r.slice(-Math.max(1,t)).reverse()}};Ze();dr();ce();wt();xe();Ze();tt();Re();var Dh="Flow revision conflict: the expectedRev is stale. Ask the AI to re-read flows and resubmit a new proposal.";function rl(e){let t=ae(e),n=Pr(e)||void 0;return{proposalId:e.id,title:t.title,displayName:t.displayName,flowId:e.payload.flowId,...n?{flowLabel:n}:{}}}a(rl,"auditProposalMeta");var wo=class{constructor(t,n,r,o){this.proposals=t;this.audit=n;this.adapter=r;this.getDeploymentType=o}static{a(this,"ProposalService")}async approve(t,n,r={}){let o=this.proposals.beginApprove(t),s=rl(o),i=r.deploymentType??this.getDeploymentType();try{let d=o.type;if(d!=="replace_flow"){let l=this.proposals.markFailed(t,n,`Unsupported proposal type '${d}'`);return this.audit.append("proposal.failed",n,{...s,reason:l.reason}),l}try{let l=await this.adapter.updateFlow({flowId:o.payload.flowId,flow:o.payload.flow,expectedRev:o.expectedRev,deploymentType:i}),c=this.proposals.markApproved(t,n,l.rev);return this.audit.append("proposal.approved",n,{...s,resultRev:l.rev,flowId:o.payload.flowId}),Pi(this.proposals,o.payload.flowId,t,l.rev),c}catch(l){if(l instanceof K){let p=this.proposals.markRejected(t,n,Dh);return this.audit.append("proposal.rejected",n,{...s,reason:p.reason,code:l.code,expectedRev:l.expectedRev,currentRev:l.currentRev}),p}let c=l instanceof Error?l.message:String(l),u=this.proposals.markFailed(t,n,c);return this.audit.append("proposal.failed",n,{...s,reason:c}),u}}finally{this.proposals.releaseLock(t)}}reject(t,n,r){let o=this.proposals.get(t);if(o.status!=="pending")throw new ne(t,o.status);let s=rl(o),i=this.proposals.markRejected(t,n,r.trim()||"Rejected by admin");return this.audit.append("proposal.rejected",n,{...s,reason:i.reason}),i}};ue();function jh(e){let t=a(()=>Promise.reject(new Ne(e)),"fail");return{kind:"http",getFlows:t,getFlow:t,updateFlow:t}}a(jh,"misconfiguredAdapter");function ol(e){let t=e.settings,n=typeof t.uiPort=="number"?t.uiPort:1880,r=t.httpAdminRoot??"/",o=!r||r==="/"?"":r.replace(/\/$/,"");return`http://127.0.0.1:${n}${o}`}a(ol,"localAdminBaseUrl");function bo(e,t={}){let n=nr(e.settings.userDir);Hs(n);let r=t.settingsStore??new Mt(n,e.settings.aiAccess,u=>e.log?.warn?.(u));t.bindSettings!==!1&&cr(r),pr(r.getEffective().publicBaseUrl);let o=t.linkStore??uo(e);o.configureDefaults({defaultTtlSeconds:r.getEffective().defaultLinkTtlSeconds,maxActiveLinks:r.getEffective().maxActiveLinks});let s=new wr(n),i=new go(n),d;if(t.flowAdapter)d=t.flowAdapter;else try{d=tl({RED:e,http:{baseUrl:ol(e)},adminAuthEnabled:z(e.settings)})}catch(u){let p=u instanceof Se?u.message:hr;e.log?.warn?.(`AI Access flow adapter unavailable: ${p}`),d=jh(p)}let l;if(t.nodeTypesAdapter)l=t.nodeTypesAdapter;else if(t.flowAdapter)l=$o();else try{l=mi({baseUrl:ol(e)})}catch(u){let p=u instanceof Error?u.message:String(u);e.log?.warn?.(`AI Access node-types adapter unavailable: ${p}`),l=$o([])}let c=new wo(s,i,d,()=>ve(e.settings).defaultDeploymentType);return{linkStore:o,proposalStore:s,auditLog:i,flowAdapter:d,nodeTypesAdapter:l,proposalService:c,settingsStore:r,...t.catalogueFetcher?{catalogueFetcher:t.catalogueFetcher}:{},...t.catalogueUrl?{catalogueUrl:t.catalogueUrl}:{}}}a(bo,"createPluginServices");G();ce();Le();rr();var sl=yn(require("dns"),1),il=yn(require("http"),1),al=yn(require("https"),1),Zn=require("net");ce();function Hh(e,t){return e===10||e===172&&t>=16&&t<=31||e===192&&t===168}a(Hh,"isPrivateV4");function Uh(e,t){return e===169&&t===254}a(Uh,"isLinkLocalV4");function Bh(e,t,n){return e===0||e===100&&t>=64&&t<=127||e>=224||e===192&&t===0&&(n===0||n===2)||e===198&&(t===18||t===19||t===51&&n===100)||e===203&&t===0&&n===113}a(Bh,"isSpecialUseV4");function qh(e){let[t=0,n=0,r=0]=e.split(".").map(Number);return t===127?"loopback":Hh(t,n)||Uh(t,n)||Bh(t,n,r)||e==="168.63.129.16"?"blocked":"public"}a(qh,"classifyIpv4");function zh(e){let t=e.split(":"),n=t.indexOf(""),r=[];for(let o of t){if(!o)continue;if(o.includes(".")){let i=o.split(".").map(Number);if(i.length!==4||i.some(d=>!Number.isInteger(d)||d<0||d>255))return;r.push((i[0]??0)<<8|(i[1]??0)),r.push((i[2]??0)<<8|(i[3]??0));continue}let s=Number.parseInt(o,16);if(!/^[\da-f]{1,4}$/i.test(o)||!Number.isInteger(s))return;r.push(s)}if(n>=0){let o=8-r.length;if(o<1)return;let s=t.slice(0,n).filter(Boolean).length;r.splice(s,0,...Array(o).fill(0))}else if(r.length!==8)return;return r.length===8?r:void 0}a(zh,"expandIpv6");function Wh(e){let t=zh(e);if(!t)return"blocked";if(t.slice(0,7).every(s=>s===0)&&t[7]===1)return"loopback";let n=t[0]??0,r=t[1]??0;return n===0||n<8192||n>16383||n===8193&&r<=511||n===8193&&r===3512||n===16383&&r<=4095||n===8194?"blocked":"public"}a(Wh,"classifyIpv6");function Qn(e){let t=ft(e);return t?(0,Zn.isIP)(t)===4?qh(t):Wh(t):"blocked"}a(Qn,"classifyPublicUrlAddress");function Gh(e){let t=e.toLowerCase().replace(/\.$/,"");return["metadata","metadata.google.internal","metadata.google.com","metadata.goog","instance-data","instance-data.ec2.internal","metadata.azure.internal","metadata.aws.internal"].some(n=>t===n||t.endsWith(`.${n}`))}a(Gh,"isMetadataHostname");async function dl(e,t,n){let r=Dt(e);if(!r)throw new Error("publicBaseUrl is required for Check");let o=new URL(r),s=o.hostname.replace(/^\[|\]$/g,"");if(Gh(s))throw new Error("Public URL Check cannot target metadata hosts");let i=ft(s),l=(typeof e=="string"?/^https?:\/\/(\[[^\]]+\]|[^/?#:]+)/i.exec(e.trim())?.[1]:void 0)?.replace(/^\[|\]$/g,"");if(i&&l&&l.toLowerCase()!==i.toLowerCase())throw new Error("Public URL Check requires canonical IP address notation");let c=s.toLowerCase().replace(/\.$/,"")==="localhost"||i!==void 0&&Qn(i)==="loopback",u=Number(o.port||(o.protocol==="https:"?443:80));if(c&&u!==t)throw new Error("Public URL Check may only target this Node-RED instance's port for loopback URLs");let p=a(g=>{let k=Qn(g);if(k==="blocked"||k==="loopback"&&!c||c&&k!=="loopback")throw new Error("Public URL Check cannot target private or non-public addresses")},"validateAddress");if(i)return p(i),{origin:r,address:i,family:(0,Zn.isIP)(i)};let f=await new Promise((g,k)=>{let T=a(()=>k(n.reason instanceof Error?n.reason:new Error(String(n.reason))),"abort");n.throwIfAborted(),n.addEventListener("abort",T,{once:!0}),sl.default.lookup(s,{all:!0},(C,L)=>{n.removeEventListener("abort",T),C?k(C):g(L)})});if(n.throwIfAborted(),!f.length)throw new Error("Public URL Check hostname has no addresses");for(let g of f)p(g.address);let m=[];for(let g of f){let k=ft(g.address);if(!k)throw new Error("Public URL Check returned an invalid address");m.some(T=>T.address===k)||m.push({address:k,family:(0,Zn.isIP)(k)})}c&&m.sort((g,k)=>g.family-k.family);let h=m[0];if(!h)throw new Error("Public URL Check hostname has no addresses");return{origin:r,...h,addresses:m}}a(dl,"preparePublicUrlCheck");function Kh(e,t,n){let r=e.hostname.replace(/^\[|\]$/g,"");return new Promise((o,s)=>{let d=(e.protocol==="https:"?al.default:il.default).request(e,{method:"GET",agent:!1,signal:n,headers:{Host:e.host},...(0,Zn.isIP)(r)?{}:{servername:r},lookup:a((l,c,u)=>{c.all?u(null,[{address:t.address,family:t.family}]):u(null,t.address,t.family)},"lookup")},l=>{o(l.statusCode??0),l.destroy()});d.on("error",s),d.end()})}a(Kh,"requestPinned");async function ll(e,t,n){let r=new URL(e.origin+t),o=e.addresses?.length?e.addresses:[{address:e.address,family:e.family}],s;for(let i of o){n.throwIfAborted();try{return await Kh(r,i,n)}catch(d){if(n.aborted)throw d;s=d}}throw s instanceof Error?s:new Error("Public URL Check request failed")}a(ll,"requestPublicUrlCheck");Sn();ue();function Jh(e){return Rn(e.settings.httpAdminRoot)}a(Jh,"mcpPageUrl");function Vh(e){return typeof e=="boolean"||typeof e=="number"||typeof e=="string"?e:Array.isArray(e)?e.join(", "):typeof e=="function"?"custom function":!1}a(Vh,"normalizeTrustProxy");function pl(e){if(!e)return"Set a Public URL before enabling OAuth.";let t=new URL(e),n=ft(t.hostname),r=t.hostname.toLowerCase().replace(/\.$/,"")==="localhost"||n==="::1"||n?.startsWith("127.");return t.protocol==="http:"&&!r?"OAuth Public URL uses HTTP; HTTPS is recommended.":null}a(pl,"oauthPublicUrlWarning");function fl(e,t,n){let{sources:r,...o}=t.getEffective(),{permission:s}=ur(e,n);return{ok:!0,adminAuthEnabled:z(e.settings),canEdit:s==="*",permission:s,mcpPageUrl:Jh(e),apiBase:fe(e.settings.httpAdminRoot),settings:o,oauthPublicUrlWarning:pl(o.publicBaseUrl),sources:r,disk:t.getDisk(),trustProxy:Vh(n.app.get("trust proxy")),precedence:["settings.js","disk","default"],defaults:{secretNamePatterns:[...ct]}}}a(fl,"settingsResponse");function ml(e,t){return(n,r)=>{r.json(fl(e,t,n))}}a(ml,"getSettingsHandler");function cl(e,t,n){let r=e[t];if(r!==void 0){if(typeof r!="boolean")throw new Error(`${t} must be a boolean`);n[t]=r}}a(cl,"parseBooleanField");function ul(e,t,n){let r=e[t];if(r!==void 0){if(typeof r!="number"||!Number.isFinite(r)||r<=0)throw new Error(`${t} must be a positive number`);n[t]=Math.floor(r)}}a(ul,"parsePositiveIntField");function Yh(e,t,n){let r=e[t];if(r===void 0)return;let o=t==="rateLimitWindowMs"?100:1,s=t==="rateLimitWindowMs"?36e5:1e6;if(typeof r!="number"||!Number.isInteger(r)||r<o||r>s)throw new Error(`${t} must be an integer between ${o} and ${s}`);n[t]=r}a(Yh,"parseRateLimitField");function Xh(e,t){if(e.corsAllowedOrigins===void 0)return;if(!Array.isArray(e.corsAllowedOrigins))throw new Error("corsAllowedOrigins must be a list of HTTP(S) origins");let n=e.corsAllowedOrigins.map(r=>vn(r));if(n.some(r=>r===void 0))throw new Error("corsAllowedOrigins must contain only HTTP(S) origins without paths, credentials, queries, or fragments");t.corsAllowedOrigins=[...new Set(n)]}a(Xh,"parseCorsField");function Zh(e,t,n){let r=e.secretNamePatterns;if(r!==void 0){if(r===null){n.push("secretNamePatterns");return}if(!Array.isArray(r)||r.some(o=>typeof o!="string"))throw new Error("secretNamePatterns must be a list of strings (or null to reset)");if(r.length>kn)throw new Error(`secretNamePatterns allows at most ${kn} entries`);for(let o of r)if(!or.test(o.trim()))throw new Error('secretNamePatterns entries must be 2-40 letters, digits, "_" or "-" (e.g. API_KEY)');t.secretNamePatterns=An(r)??[]}}a(Zh,"parseSecretPatternsField");function hl(e,t,n){return(r,o)=>{let s=r.body;if(!s||typeof s!="object"||Array.isArray(s)){o.status(400).json({error:"Request body must be a settings object",code:"invalid_body"});return}let i={...s};for(let[c,u]of Object.entries(t.getEffective().sources))u==="settings.js"&&delete i[c];t.getEffective().sources.oauth==="settings.js"&&delete i.oauth;let d={},l=[];try{if(i.oauth!==void 0){if(!i.oauth||typeof i.oauth!="object"||Array.isArray(i.oauth))throw new Error("oauth must be an object");let c=i.oauth,u={};if(c.enabled!==void 0){if(typeof c.enabled!="boolean")throw new Error("oauth.enabled must be a boolean");u.enabled=c.enabled}for(let f of["accessTokenTtlSec","refreshIdleTtlSec","dcrMaxClients"])if(c[f]!==void 0){if(typeof c[f]!="number"||!Number.isInteger(c[f])||c[f]<1||c[f]>31536e3)throw new Error(`oauth.${f} must be a positive integer`);u[f]=c[f]}let p=c.refreshMaxLifetimeSec;if(p!==void 0){if(typeof p!="number"||!Number.isInteger(p)||p<0||p>31536e4)throw new Error("oauth.refreshMaxLifetimeSec must be a non-negative integer");u.refreshMaxLifetimeSec=p}d.oauth=u}cl(i,"enabled",d),cl(i,"allowWithoutAdminAuth",d),ul(i,"defaultLinkTtlSeconds",d),ul(i,"maxActiveLinks",d)}catch(c){o.status(400).json({error:c.message,code:"invalid_body"});return}if(i.defaultDeploymentType!==void 0){let c=i.defaultDeploymentType;if(c!=="nodes"&&c!=="flows"&&c!=="full"){o.status(400).json({error:"defaultDeploymentType must be 'nodes', 'flows', or 'full'",code:"invalid_body"});return}d.defaultDeploymentType=c}try{i.publicBaseUrl!==void 0&&(d.publicBaseUrl=Dt(i.publicBaseUrl)),Xh(i,d),Zh(i,d,l);for(let c of["rateLimitWindowMs","rateLimitMaxPerIp","rateLimitMaxPerToken"])Yh(i,c,d)}catch(c){o.status(400).json({error:c.message,code:"invalid_body"});return}try{let c=t.patch(d,l);pr(c.publicBaseUrl),n.configureDefaults({defaultTtlSeconds:c.defaultLinkTtlSeconds,maxActiveLinks:c.maxActiveLinks});let u=fl(e,t,r),p=c.publicBaseUrl?new URL(c.publicBaseUrl):void 0,f=p?ft(p.hostname.replace(/^\[|\]$/g,"")):void 0,m=f?Qn(f):void 0,h=[];m!==void 0&&m!=="public"&&h.push("Public URL uses a private or non-public IP address.");let g=pl(c.publicBaseUrl);g&&c.publicBaseUrl&&h.push(g),o.json({...u,...h.length?{warnings:h}:{}})}catch(c){if(c instanceof Y){o.status(c.statusCode).json({error:c.message,code:c.code});return}D(o,c);return}}}a(hl,"putSettingsHandler");function gl(e){return async(t,n)=>{let r=new AbortController,o=setTimeout(()=>r.abort(),5e3),s;try{let i;try{let c=e.settings,p=e.server?.address(),f=t.body?.publicBaseUrl,m=typeof f=="string"?new URL(f.trim()).hostname:"";if((m.toLowerCase().replace(/^\[|\]$/g,"").replace(/\.$/,"")==="localhost"||Qn(m.replace(/^\[|\]$/g,""))==="loopback")&&typeof p=="string")throw new Error("Public URL Check does not support loopback URLs when Node-RED listens on a Unix socket");let g=p&&typeof p=="object"?p.port:c.uiPort??1880;i=await dl(t.body?.publicBaseUrl,g,r.signal)}catch(c){if(r.signal.aborted)throw c;n.status(400).json({error:c.message,code:"invalid_body"});return}s=i.origin+fe(e.settings.httpAdminRoot);let d=await ll(i,fe(e.settings.httpAdminRoot),r.signal),l={status:d,ok:d>=200&&d<300};n.json({ok:l.ok,status:l.status,url:s,message:l.ok?"Discovery endpoint is reachable":`Discovery endpoint returned HTTP ${l.status}`})}catch{n.json({ok:!1,status:null,url:s,error:r.signal.aborted?"Public URL check timed out after 5 seconds":"Could not reach the discovery endpoint"})}finally{clearTimeout(o)}}}a(gl,"checkPublicUrlHandler");ue();pt();ue();J();dr();var Qh="com55-ai-access";function wl(e){let t=new Set;for(let n of String(e??"").split(",")){let[r,...o]=n.split(";").map(d=>d.trim().toLowerCase());if(!r)continue;let s=o.find(d=>d.startsWith("q=")),i=s?Number(s.slice(2)):1;Number.isFinite(i)&&i>0&&t.add(r)}return t}a(wl,"acceptedMediaTypes");function bl(e){let t=wl(e.headers.accept);return t.has("text/event-stream")&&!t.has("text/html")}a(bl,"isMcpStreamRequest");function eg(e,t,n){e.httpAdmin.get("/mcp/api/v1",t,n,ls(e)),e.httpAdmin.get("/mcp/api/v1/",t,n,ls(e)),e.httpAdmin.get("/mcp/api/v1/openapi.yaml",t,n,Md()),e.httpAdmin.get("/mcp/api/v1/openapi.json",t,n,Dd()),e.httpAdmin.get("/mcp/editor/ai-access-sidebar.js",t,(r,o)=>{o.setHeader("Cache-Control","no-store, no-cache, must-revalidate, max-age=0"),o.setHeader("Pragma","no-cache"),o.setHeader("Expires","0"),o.setHeader("X-AI-Access-Sidebar-Version",on),o.type("application/javascript").status(200).send(Hr())})}a(eg,"registerMcpShellAndDiscovery");var tg=/^\/mcp\/(?:api\/v1\/proposals|rpc$|a\/[^/]+\/(?:proposals|propose|draft))/;function vl(e,t={}){let n=bo(e,{...t,linkStore:t.linkStore??uo(e)}),r=xa(()=>n.settingsStore.getEffective()),{ip:o,token:s}=r,i=new po(nr(e.settings.userDir),()=>n.settingsStore.getEffective().oauth.dcrMaxClients),d=a(()=>n.settingsStore.getEffective().publicBaseUrl,"publicBase"),l=a(()=>`${d().replace(/\/$/,"")}/mcp`,"mcpUrl"),c=new fo(i,l,()=>n.settingsStore.getEffective().oauth.accessTokenTtlSec,()=>n.settingsStore.getEffective().oauth.refreshIdleTtlSec,()=>n.settingsStore.getEffective().oauth.refreshMaxLifetimeSec),u=Nd(e,c,d,l);Bs(e.httpAdmin,Us(()=>n.settingsStore.getEffective().corsAllowedOrigins));let p=a(w=>(Array.isArray(w)?w:w?[w]:[]).flatMap(v=>{if(typeof v=="string")return[v];let A=v;return A&&typeof A.path=="string"?[{path:A.path,root:typeof A.root=="string"?A.root:void 0}]:[]}),"staticMounts"),f={authorization:[],metadata:[],details:[],httpNodeRootFallback:e.settings.httpNodeRoot===!1||!e.httpNode},m="",h=a(()=>{let w=Id({httpAdmin:e.httpAdmin,httpNode:e.httpNode,httpAdminRoot:e.settings.httpAdminRoot,httpNodeRoot:e.settings.httpNodeRoot,metadataFallback:f.httpNodeRootFallback,eachNode:e.nodes?F=>e.nodes?.eachNode(F):void 0,staticDirs:p(e.settings.httpStatic),httpStaticRoot:e.settings.httpStaticRoot,fileExists:yl.existsSync}),v=w.authorization,A=w.metadata;f.authorization=v.map(Ls),f.metadata=A.map(Ls),f.details=[...v.map(F=>({...F,kind:"authorization"})),...A.map(F=>({...F,kind:"metadata"}))];let E=[...f.authorization.map(F=>`${F} \u2014 OAuth authorization routes disabled`),...f.metadata.map(F=>`${F} \u2014 OAuth metadata disabled`)],$=E.join(`
|
|
6823
|
-
`);if(
|
|
6973
|
+
</html>`}a(hc,"renderMcpPage");Fe();et();Te();Je();se();$t();ie();tt();zt();Fe();Io();et();Pe();function Ue(e,t,n,r={}){e.setHeader("X-Content-Type-Options","nosniff"),e.setHeader("Content-Type","application/json; charset=utf-8");let o={error:nt[n]??"Request failed",code:n,...r},s=we(n,"rest");s&&typeof o.hint!="string"&&(o.hint=s),e.status(t).json(o)}a(Ue,"sendSafeJsonError");function zi(e){let t={kind:"admin"};return typeof e.user?.username=="string"&&(t.username=e.user.username),t}a(zi,"adminActor");function mc(e,t,n,r,o){return async(s,i)=>{if(!s.accessLink){i.setHeader("WWW-Authenticate",ce),i.status(401).json({error:xe,code:qe});return}try{let d=V(s,r),l=ge(s.query),c=await Tt(e,t,s.accessLink,s.body??{},n,d,o),u=ne(e),p=Ve(e,e.get(c.id),s.accessLink.id),f=c.warnings;i.status(201).json({...fe(c,{...d,include:l}),mine:!0,...Ye(p),...ue(u),...f&&f.length>0?{warnings:f}:{}})}catch(d){let l=W(d,"rest");if(l){i.status(l.statusCode).json(l.body);return}B(i,d);return}}}a(mc,"createProposalHandler");function gc(e,t,n){return async(r,o)=>{let s=await Qe(t),i=s?.rev,d=s?co(s.flows):void 0,l={...V(r,n),...d?{liveFlowLabels:d}:{}},c=ge(r.query),u;try{u=xo(r.query)}catch(x){B(o,x);return}let p=s?$e(e,s).expired:ne(e),f=r.accessLink;if(f){let x=Tn(e,f,l,u),$=x.proposals.map(L=>{let D=he(e,f,L,{...l,include:c},{includeConflicts:!1});return!U(D.status)||i===void 0?D:{...D,baseRevStale:D.expectedRev!==i}});o.json({proposals:$,...Jt({...x,returned:x.proposals.length}),...ue(p)});return}let h={};u.flowId&&(h.flowId=u.flowId);let m=Da(e.list(h).filter(x=>di(x.status,u.status)),{...l,include:c}),g=u.offset??0,A=(u.limit!==void 0?m.slice(g,g+u.limit):g>0?m.slice(g):m).map(x=>!U(x.status)||i===void 0?x:{...x,baseRevStale:x.expectedRev!==i});o.json({proposals:A,...Jt({total:m.length,limit:u.limit??null,offset:g,returned:A.length}),...ue(p)})}}a(gc,"listProposalsHandler");function yc(e,t,n){return async(r,o)=>{let s=r.params.id;if(typeof s!="string"||s.length===0){o.status(400).json({error:"Missing proposal id"});return}let i=V(r,t),d=ge(r.query),l=await Qe(n);try{if(r.accessLink){let c=ct(e,r.accessLink,s,i);o.json(he(e,r.accessLink,c,{...i,include:d},{...l?{live:l}:{}}));return}l?$e(e,l):ne(e),o.json(fe(e.get(s),{...i,include:d}))}catch(c){if(c instanceof me){Ue(o,404,c.code);return}let u=W(c,"rest");if(u){o.status(u.statusCode).json(u.body);return}B(o,c);return}}}a(yc,"getProposalHandler");function wc(e,t){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){r.status(400).json({error:"Missing proposal id"});return}let s=n.body??{},i={};if(s.mergedHash!==void 0){if(typeof s.mergedHash!="string"||!/^[0-9a-f]{8,64}$/.test(s.mergedHash)){r.status(400).json({error:"mergedHash must be a hex string",code:"invalid_body"});return}i.mergedHash=s.mergedHash}if(s.deploymentType!==void 0){if(s.deploymentType!=="nodes"&&s.deploymentType!=="flows"&&s.deploymentType!=="full"){r.status(400).json({error:"deploymentType must be 'nodes', 'flows', or 'full'"});return}i.deploymentType=s.deploymentType}try{let d=await e.approve(o,zi(n),i);r.json(pe(d,V(n,t)))}catch(d){if(d instanceof Ke){Ue(r,409,d.code,{conflicts:d.conflicts});return}if(d instanceof Yt){Ue(r,409,d.code,{mergedHash:d.mergedHash,rebaseState:d.rebaseState});return}if(d instanceof Xt||d instanceof Pt||d instanceof Zt){Ue(r,d.statusCode,d.code);return}if(d instanceof me||d instanceof Y||d instanceof Re||d instanceof le){Ue(r,d.statusCode,d.code);return}B(r,d);return}}}a(wc,"approveProposalHandler");function bc(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=pe(e.reject(o,zi(n),i),V(n,t));r.json(d)}catch(d){if(d instanceof me||d instanceof Y||d instanceof le){Ue(r,d.statusCode,d.code);return}B(r,d);return}}}a(bc,"rejectProposalHandler");function vc(e,t,n){return(r,o)=>{if(!r.accessLink){o.setHeader("WWW-Authenticate",ce),o.status(401).json({error:xe,code:qe});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=V(r,n),l=ge(r.query);try{let c=Rn(e,t,r.accessLink,s,i.reason,d);o.json(he(e,r.accessLink,c,{...d,include:l}))}catch(c){let u=W(c,"rest");if(u){Ue(o,u.statusCode,u.body.code);return}Ue(o,500,"internal_error");return}}}a(vc,"withdrawProposalHandler");function kc(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?ct(e,n.accessLink,o):e.get(o),i=In(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=Ln(await $n(s,Xe(n,t),e.get(o)),i.format);r.json(d)}catch(s){if(s instanceof me){Ue(r,404,s.code);return}if(s instanceof X||s instanceof Le||s instanceof ze){Ue(r,s.statusCode,s.code);return}let i=W(s,"rest");if(i){r.status(i.statusCode).json(i.body);return}B(r,s);return}}}a(kc,"getProposalDiffHandler");function Ac(e,t,n,r,o){return async(s,i)=>{if(!s.accessLink){Ue(i,401,"access_link_missing");return}try{let d=V(s,r),l=await To(e,t,s.accessLink,String(s.params.id),s.body??{},n,d,o);i.json(he(e,s.accessLink,l,d))}catch(d){B(i,d)}}}a(Ac,"resolveProposalHandler");function Sc(e,t,n,r){return async(o,s)=>{try{let i=o.body??{},d=await Ld(e,t,zi(o),String(o.params.id),i.nodeId,i.choice,n);s.json(fe(d,V(o,r)))}catch(i){B(s,i)}}}a(Sc,"resolveProposalConflictHandler");function xc(e,t,n){return async(r,o)=>{try{ne(e);let s=e.get(String(r.params.id)),i=await Qe(t);i&&U(s.status)&&(s=Ze(e,s,i).proposal),o.json(fe(s,V(r,n)))}catch(s){B(o,s)}}}a(xc,"recheckProposalHandler");Pe();Te();Te();ro();function ly(e){return e.endsWith("/")?e.slice(0,-1):e}a(ly,"trimTrailingSlash");var as=class{static{a(this,"HttpAdapter")}kind="http";baseUrl;accessToken;fetchImpl;constructor(t){this.baseUrl=ly(t.baseUrl),this.accessToken=t.accessToken,this.fetchImpl=t.fetch??globalThis.fetch.bind(globalThis)}headers(t){let n=new Headers(t);return n.set("Accept","application/json"),n.set("Node-RED-API-Version","v2"),this.accessToken&&n.set("Authorization",`Bearer ${this.accessToken}`),n}async request(t,n={}){return await this.fetchImpl(`${this.baseUrl}${t}`,{...n,headers:this.headers(n.headers)})}async readJson(t,n){let r=await t.text();if(!t.ok){let o=r;try{let s=JSON.parse(r);o=s.message??s.code??r}catch{}throw t.status===404?new X(n):t.status===409?new te("unknown","unknown",`Admin API ${n}`):t.status===401||t.status===403?new ze(Yr):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 or(n,"GET /flows")}async getFlow(t){let n=encodeURIComponent(t),[r,o]=await Promise.all([this.request("/flows"),this.request(`/flow/${n}`)]);if(o.status===404)throw new X(t);let s=await this.readJson(r,"GET /flows"),i=await this.readJson(o,`GET /flow/${t}`);return{rev:or(s,"GET /flows").rev,flow:yn(t,i)}}async updateFlow(t){let n=t.deploymentType??"nodes",r=await this.getFlows();if(r.rev!==t.expectedRev)throw new te(t.expectedRev,r.rev,"updateFlow");let o=no(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 te(t.expectedRev,d,"updateFlow")}let i=await this.readJson(s,"POST /flows");if(!i||typeof i!="object"||typeof i.rev!="string")throw new Error("Admin API POST /flows did not return a rev");return{flowId:t.flowId,rev:i.rev}}};Te();ro();function cy(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(cy,"requireFlowsApi");function uy(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(uy,"resolveRev");var ds=class{static{a(this,"RedAdapter")}kind="red";flows;constructor(t){this.flows=cy(t)}async getFlows(){let t=await this.flows.getFlows({});return or(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 py(o)?new X(t):o}return{rev:n.rev,flow:yn(t,r)}}async updateFlow(t){let n=t.deploymentType??"nodes",r=await this.getFlows();if(r.rev!==t.expectedRev)throw new te(t.expectedRev,r.rev,"updateFlow");let o=no(r.flows,t.flowId,t.flow),s;try{s=await this.flows.setFlows({flows:{flows:o,rev:t.expectedRev},deploymentType:n})}catch(i){if(i&&typeof i=="object"&&"code"in i&&i.code==="version_mismatch"){let d=await this.getFlows();throw new te(t.expectedRev,d.rev,"updateFlow")}throw i}return{flowId:t.flowId,rev:uy(s)}}};function Tc(e){let t=e?.runtime?.flows;return!!(t?.getFlows&&t.getFlow&&t.setFlows)}a(Tc,"canUseRedRuntime");function py(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(py,"isRuntimeFlowNotFound");var Rc=require("module");function Pc(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(Pc,"isFlowsApi");function fy(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,Rc.createRequire)(e)}a(fy,"createPluginRequire");function hy(e){try{let t=e("@node-red/runtime/lib/settings");return typeof t.available=="function"&&t.available()===!0}catch{return!1}}a(hy,"isNodeRedRuntimeInitialized");function my(){try{let e=fy();if(!hy(e))return null;let t=e("@node-red/runtime");return Pc(t.flows)?t.flows:null}catch{return null}}a(my,"tryRequireInitializedRuntimeFlows");function _c(e){if(e?.runtime&&Pc(e.runtime.flows))return e;let t=my();return t?{runtime:{flows:t}}:null}a(_c,"resolveRuntimeHost");function Ec(e={}){let t=_c(e.RED??null);if(t&&Tc(t))return new ds(t);let n=e.adminAuthEnabled===!0;if(e.http?.baseUrl){if(n&&!e.http.accessToken)throw new Le("In-process Node-RED flow runtime is unavailable and HTTP Admin API fallback has no credentials while adminAuth is enabled. Ensure the plugin runs inside Node-RED so @node-red/runtime.flows can be used.");return new as(e.http)}throw new Le("No usable Node-RED flow adapter: in-process flows API is unavailable and no HTTP Admin API baseUrl was provided")}a(Ec,"createFlowAdapter");Te();Rs();var cs=require("fs"),Cc=require("path");Zr();se();var gy=/^[a-z][a-z0-9._-]{0,63}$/;function yy(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(yy,"sanitizeActor");var ls=class{constructor(t,n={}){this.paths=t;this.filePath=(0,Cc.join)(t.logs,"audit.jsonl"),this.now=n.now??(()=>Date.now()),this.maxAppendBytes=n.maxAppendBytes??Vi}static{a(this,"AuditLog")}filePath;now;maxAppendBytes;seq=0;append(t,n,r){let o=gy.test(t)?t:"unknown";this.seq+=1;let s={id:`log-${this.now()}-${this.seq}`,at:new Date(this.now()).toISOString(),action:o,actor:yy(n)};r!==void 0&&(s.details=ha(r));let i=ft(s,this.maxAppendBytes,{kind:"audit"});return Xi(this.paths.root,this.filePath,i),s}list(t=100){if(!(0,cs.existsSync)(this.filePath))return[];let n=(0,cs.readFileSync)(this.filePath,"utf8").split(`
|
|
6974
|
+
`).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()}};Je();qr();ve();qt();Io();ke();function wy(e){let t=a(()=>Promise.reject(new ze(e)),"fail");return{kind:"http",getFlows:t,getFlow:t,updateFlow:t}}a(wy,"misconfiguredAdapter");function Ic(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(Ic,"localAdminBaseUrl");function us(e,t={}){let n=Mr(e.settings.userDir);Zi(n);let r=t.settingsStore??new fn(n,e.settings.aiAccess,u=>e.log?.warn?.(u));t.bindSettings!==!1&&Wr(r),Kr(r.getEffective().publicBaseUrl);let o=t.linkStore??rs(e);o.configureDefaults({defaultTtlSeconds:r.getEffective().defaultLinkTtlSeconds,maxActiveLinks:r.getEffective().maxActiveLinks});let s=new Qr(n),i=new ls(n),d;if(t.flowAdapter)d=t.flowAdapter;else try{d=Ec({RED:e,http:{baseUrl:Ic(e)},adminAuthEnabled:oe(e.settings)})}catch(u){let p=u instanceof Le?u.message:Yr;e.log?.warn?.(`AI Access flow adapter unavailable: ${p}`),d=wy(p)}d=wa(d);let l;if(t.nodeTypesAdapter)l=t.nodeTypesAdapter;else if(t.flowAdapter)l=Ls();else try{l=Pa({baseUrl:Ic(e)})}catch(u){let p=u instanceof Error?u.message:String(u);e.log?.warn?.(`AI Access node-types adapter unavailable: ${p}`),l=Ls([])}let c=new Co(s,i,d,()=>Ee(e.settings).defaultDeploymentType,e.settings.aiAccess);return{linkStore:o,proposalStore:s,auditLog:i,flowAdapter:d,nodeTypesAdapter:l,proposalService:c,settingsStore:r,...t.catalogueFetcher?{catalogueFetcher:t.catalogueFetcher}:{},...t.catalogueUrl?{catalogueUrl:t.catalogueUrl}:{}}}a(us,"createPluginServices");se();ve();Be();Dr();var Lc=zn(require("dns"),1),Nc=zn(require("http"),1),Fc=zn(require("https"),1),Nr=require("net");ve();function by(e,t){return e===10||e===172&&t>=16&&t<=31||e===192&&t===168}a(by,"isPrivateV4");function vy(e,t){return e===169&&t===254}a(vy,"isLinkLocalV4");function ky(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(ky,"isSpecialUseV4");function Ay(e){let[t=0,n=0,r=0]=e.split(".").map(Number);return t===127?"loopback":by(t,n)||vy(t,n)||ky(t,n,r)||e==="168.63.129.16"?"blocked":"public"}a(Ay,"classifyIpv4");function Sy(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(Sy,"expandIpv6");function xy(e){let t=Sy(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(xy,"classifyIpv6");function Fr(e){let t=Mt(e);return t?(0,Nr.isIP)(t)===4?Ay(t):xy(t):"blocked"}a(Fr,"classifyPublicUrlAddress");function Ty(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(Ty,"isMetadataHostname");async function Oc(e,t,n){let r=hn(e);if(!r)throw new Error("publicBaseUrl is required for Check");let o=new URL(r),s=o.hostname.replace(/^\[|\]$/g,"");if(Ty(s))throw new Error("Public URL Check cannot target metadata hosts");let i=Mt(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&&Fr(i)==="loopback",u=Number(o.port||(o.protocol==="https:"?443:80));if(c&&u!==t)throw new Error("Public URL Check may only target this Node-RED instance's port for loopback URLs");let p=a(g=>{let y=Fr(g);if(y==="blocked"||y==="loopback"&&!c||c&&y!=="loopback")throw new Error("Public URL Check cannot target private or non-public addresses")},"validateAddress");if(i)return p(i),{origin:r,address:i,family:(0,Nr.isIP)(i)};let f=await new Promise((g,y)=>{let A=a(()=>y(n.reason instanceof Error?n.reason:new Error(String(n.reason))),"abort");n.throwIfAborted(),n.addEventListener("abort",A,{once:!0}),Lc.default.lookup(s,{all:!0},(x,$)=>{n.removeEventListener("abort",A),x?y(x):g($)})});if(n.throwIfAborted(),!f.length)throw new Error("Public URL Check hostname has no addresses");for(let g of f)p(g.address);let h=[];for(let g of f){let y=Mt(g.address);if(!y)throw new Error("Public URL Check returned an invalid address");h.some(A=>A.address===y)||h.push({address:y,family:(0,Nr.isIP)(y)})}c&&h.sort((g,y)=>g.family-y.family);let m=h[0];if(!m)throw new Error("Public URL Check hostname has no addresses");return{origin:r,...m,addresses:h}}a(Oc,"preparePublicUrlCheck");function Ry(e,t,n){let r=e.hostname.replace(/^\[|\]$/g,"");return new Promise((o,s)=>{let d=(e.protocol==="https:"?Fc.default:Nc.default).request(e,{method:"GET",agent:!1,signal:n,headers:{Host:e.host},...(0,Nr.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(Ry,"requestPinned");async function $c(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 Ry(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($c,"requestPublicUrlCheck");un();ke();function Py(e){return Zn(e.settings.httpAdminRoot)}a(Py,"mcpPageUrl");function _y(e){return typeof e=="boolean"||typeof e=="number"||typeof e=="string"?e:Array.isArray(e)?e.join(", "):typeof e=="function"?"custom function":!1}a(_y,"normalizeTrustProxy");function Hc(e){if(!e)return"Set a Public URL before enabling OAuth.";let t=new URL(e),n=Mt(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(Hc,"oauthPublicUrlWarning");function jc(e,t,n){let{sources:r,...o}=t.getEffective(),{permission:s}=Gr(e,n);return{ok:!0,adminAuthEnabled:oe(e.settings),canEdit:s==="*",permission:s,mcpPageUrl:Py(e),apiBase:Se(e.settings.httpAdminRoot),settings:o,oauthPublicUrlWarning:Hc(o.publicBaseUrl),sources:r,disk:t.getDisk(),trustProxy:_y(n.app.get("trust proxy")),precedence:["settings.js","disk","default"],defaults:{secretNamePatterns:[...Ft]}}}a(jc,"settingsResponse");function Uc(e,t){return(n,r)=>{r.json(jc(e,t,n))}}a(Uc,"getSettingsHandler");function Mc(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(Mc,"parseBooleanField");function Dc(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(Dc,"parsePositiveIntField");function Ey(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(Ey,"parseRateLimitField");function Cy(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=>Kn(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(Cy,"parseCorsField");function Iy(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>Jn)throw new Error(`secretNamePatterns allows at most ${Jn} entries`);for(let o of r)if(!Hr.test(o.trim()))throw new Error('secretNamePatterns entries must be 2-40 letters, digits, "_" or "-" (e.g. API_KEY)');t.secretNamePatterns=Vn(r)??[]}}a(Iy,"parseSecretPatternsField");function Bc(e,t,n){return(r,o)=>{let s=r.body;if(!s||typeof s!="object"||Array.isArray(s)){o.status(400).json({error:"Request body must be a settings object",code:"invalid_body"});return}let i={...s};for(let[c,u]of Object.entries(t.getEffective().sources))u==="settings.js"&&delete i[c];t.getEffective().sources.oauth==="settings.js"&&delete i.oauth;let d={},l=[];try{if(i.proposalTtlDays!==void 0){let c=i.proposalTtlDays;if(typeof c!="number"||!Number.isInteger(c)||c<1||c>365)throw new Error("proposalTtlDays must be an integer between 1 and 365");d.proposalTtlDays=c}if(i.oauth!==void 0){if(!i.oauth||typeof i.oauth!="object"||Array.isArray(i.oauth))throw new Error("oauth must be an object");let c=i.oauth,u={};if(c.enabled!==void 0){if(typeof c.enabled!="boolean")throw new Error("oauth.enabled must be a boolean");u.enabled=c.enabled}for(let f of["accessTokenTtlSec","refreshIdleTtlSec","dcrMaxClients"])if(c[f]!==void 0){if(typeof c[f]!="number"||!Number.isInteger(c[f])||c[f]<1||c[f]>31536e3)throw new Error(`oauth.${f} must be 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}Mc(i,"enabled",d),Mc(i,"allowWithoutAdminAuth",d),Dc(i,"defaultLinkTtlSeconds",d),Dc(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=hn(i.publicBaseUrl)),Cy(i,d),Iy(i,d,l);for(let c of["rateLimitWindowMs","rateLimitMaxPerIp","rateLimitMaxPerToken"])Ey(i,c,d)}catch(c){o.status(400).json({error:c.message,code:"invalid_body"});return}try{let c=t.patch(d,l);Kr(c.publicBaseUrl),n.configureDefaults({defaultTtlSeconds:c.defaultLinkTtlSeconds,maxActiveLinks:c.maxActiveLinks});let u=jc(e,t,r),p=c.publicBaseUrl?new URL(c.publicBaseUrl):void 0,f=p?Mt(p.hostname.replace(/^\[|\]$/g,"")):void 0,h=f?Fr(f):void 0,m=[];h!==void 0&&h!=="public"&&m.push("Public URL uses a private or non-public IP address.");let g=Hc(c.publicBaseUrl);g&&c.publicBaseUrl&&m.push(g),o.json({...u,...m.length?{warnings:m}:{}})}catch(c){if(c instanceof le){o.status(c.statusCode).json({error:c.message,code:c.code});return}B(o,c);return}}}a(Bc,"putSettingsHandler");function qc(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,h=typeof f=="string"?new URL(f.trim()).hostname:"";if((h.toLowerCase().replace(/^\[|\]$/g,"").replace(/\.$/,"")==="localhost"||Fr(h.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 Oc(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+Se(e.settings.httpAdminRoot);let d=await $c(i,Se(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(qc,"checkPublicUrlHandler");ke();$t();ke();ie();qr();var Ly="com55-ai-access",ps=new WeakMap;function Wc(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(Wc,"acceptedMediaTypes");function Gc(e){let t=Wc(e.headers.accept);return t.has("text/event-stream")&&!t.has("text/html")}a(Gc,"isMcpStreamRequest");function Ny(e,t,n){e.httpAdmin.get("/mcp/api/v1",t,n,mi(e)),e.httpAdmin.get("/mcp/api/v1/",t,n,mi(e)),e.httpAdmin.get("/mcp/api/v1/openapi.yaml",t,n,lc()),e.httpAdmin.get("/mcp/api/v1/openapi.json",t,n,cc()),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",Cn),o.type("application/javascript").status(200).send(Lo())})}a(Ny,"registerMcpShellAndDiscovery");var Fy=/^\/mcp\/(?:api\/v1\/proposals|rpc$|a\/[^/]+\/(?:proposals|propose|draft))/;function Kc(e,t={}){let n=us(e,{...t,linkStore:t.linkStore??rs(e)});gd(()=>n.settingsStore.getEffective().proposalTtlDays),ps.get(e)?.();let r=wd(()=>wr(n.proposalStore,n.flowAdapter),500),o=a(()=>{r.cancel(),e.events?.removeListener?.("flows:started",r.trigger)},"cleanup");ps.set(e,o),e.events?.on("flows:started",r.trigger);let s=Qd(()=>n.settingsStore.getEffective()),{ip:i,token:d}=s,l=new ss(Mr(e.settings.userDir),()=>n.settingsStore.getEffective().oauth.dcrMaxClients),c=a(()=>n.settingsStore.getEffective().publicBaseUrl,"publicBase"),u=a(()=>`${c().replace(/\/$/,"")}/mcp`,"mcpUrl"),p=new is(l,u,()=>n.settingsStore.getEffective().oauth.accessTokenTtlSec,()=>n.settingsStore.getEffective().oauth.refreshIdleTtlSec,()=>n.settingsStore.getEffective().oauth.refreshMaxLifetimeSec),f=sc(e,p,c,u);ea(e.httpAdmin,Qi(()=>n.settingsStore.getEffective().corsAllowedOrigins));let h=a(v=>(Array.isArray(v)?v:v?[v]:[]).flatMap(S=>{if(typeof S=="string")return[S];let N=S;return N&&typeof N.path=="string"?[{path:N.path,root:typeof N.root=="string"?N.root:void 0}]:[]}),"staticMounts"),m={authorization:[],metadata:[],details:[],httpNodeRootFallback:e.settings.httpNodeRoot===!1||!e.httpNode},g="",y=a(()=>{let v=oc({httpAdmin:e.httpAdmin,httpNode:e.httpNode,httpAdminRoot:e.settings.httpAdminRoot,httpNodeRoot:e.settings.httpNodeRoot,metadataFallback:m.httpNodeRootFallback,eachNode:e.nodes?I=>e.nodes?.eachNode(I):void 0,staticDirs:h(e.settings.httpStatic),httpStaticRoot:e.settings.httpStaticRoot,fileExists:zc.existsSync}),S=v.authorization,N=v.metadata;m.authorization=S.map(ji),m.metadata=N.map(ji),m.details=[...S.map(I=>({...I,kind:"authorization"})),...N.map(I=>({...I,kind:"metadata"}))];let w=[...m.authorization.map(I=>`${I} \u2014 OAuth authorization routes disabled`),...m.metadata.map(I=>`${I} \u2014 OAuth metadata disabled`)],F=w.join(`
|
|
6975
|
+
`);if(F!==g)for(let I of w)e.log?.warn?.(`AI Access OAuth route collision: ${I}`);g=F},"refreshCollisions");y(),e.events?.on("flows:started",y);let A=bi({windowMs:6e4,maxPerIp:30}),x=a((v,S,N)=>{if(!n.settingsStore.getEffective().oauth.enabled||m.authorization.length||!v.path.startsWith("/mcp/oauth/")){N();return}let w=v.path.replace(/\/+$/,"").toLowerCase(),F=a(()=>mn(e)(v,S,()=>f.server(v,S,N)),"run");w==="/mcp/oauth/authorize"?i(v,S,()=>A(v,S,F)):w.startsWith("/mcp/oauth/consent/")?i(v,S,F):F()},"oauthServerMiddleware");x.oauthOwned=!0,e.httpAdmin.use(x);let $=a((v,S,N)=>{if(!n.settingsStore.getEffective().oauth.enabled||m.metadata.length){N();return}f.metadata(v,S,N)},"metadataHandler");$.oauthOwned=!0,m.httpNodeRootFallback?e.httpAdmin.use("/mcp",$):e.httpNode?.use($),e.httpAdmin.get("/mcp/api/v1/oauth/clients",i,d,Ie(e,"api"),(v,S)=>S.json({clients:l.data.clients.map(N=>({client_id:N.client_id,client_name:N.client_name,redirect_uris:N.redirect_uris,lastUsedAt:N.lastUsedAt}))})),e.httpAdmin.get("/mcp/api/v1/oauth/status",i,d,Ie(e,"api"),(v,S)=>{let N=!oe(e.settings)||mt(v)==="*";S.json({...m,details:N?m.details:m.details.map(({file:w,...F})=>F),collisions:[...m.authorization,...m.metadata],oauthEnabled:n.settingsStore.getEffective().oauth.enabled})}),e.httpAdmin.delete("/mcp/api/v1/oauth/clients/:id",i,d,Ie(e,"api"),Qn(e),(v,S)=>S.json({revoked:l.revokeClient(String(v.params.id))})),e.httpAdmin.use((v,S,N)=>{if(!Fy.test(v.path)){N();return}n.flowAdapter.getFlows().then(w=>$a(v,co(w.flows))).catch(()=>{}).finally(()=>N())}),e.httpAdmin.use(Ul(n.auditLog)),Al(e,{linkStore:n.linkStore,proposalStore:n.proposalStore,auditLog:n.auditLog,flowAdapter:kt(n.flowAdapter),nodeTypesAdapter:n.nodeTypesAdapter,...n.catalogueFetcher?{catalogueFetcher:n.catalogueFetcher}:{},...n.catalogueUrl?{catalogueUrl:n.catalogueUrl}:{},rateLimiters:s}),e.httpAdmin.get("/ai-access",i,d,(v,S)=>{S.append("Content-Security-Policy","form-action 'none'; frame-ancestors 'none'"),S.setHeader("X-Frame-Options","DENY"),S.setHeader("Cache-Control","no-store, no-cache, must-revalidate, max-age=0"),S.setHeader("Pragma","no-cache"),S.setHeader("Expires","0"),S.type("html").status(200).send(hc({apiBase:Se(e.settings.httpAdminRoot),mcpRpc:ht(e.settings.httpAdminRoot),editorRoot:Ae(e.settings.httpAdminRoot),authTokenKey:Vr(e.settings.httpAdminRoot),dashboardPath:Zn(e.settings.httpAdminRoot)}))}),e.httpAdmin.get("/mcp",i,(v,S,N)=>{if(Gc(v)){N();return}S.setHeader("Cache-Control","no-store"),S.redirect(302,Zn(e.settings.httpAdminRoot))}),Ny(e,i,d),e.httpAdmin.get("/mcp/api/v1/status",i,d,ia(e,v=>n.linkStore.validateResult(v)),O((v,S)=>{S.json(Mn(e,v))})),e.httpAdmin.get("/mcp/api/v1/settings",i,d,Ie(e,"api"),O(Uc(e,n.settingsStore))),e.httpAdmin.put("/mcp/api/v1/settings",i,d,Ie(e,"api"),Qn(e),De("rest"),O(Bc(e,n.settingsStore,n.linkStore))),e.httpAdmin.post("/mcp/api/v1/settings/check-public-url",i,d,Ie(e,"api"),Qn(e),De("rest"),O(qc(e)));let L=[i,d,Ie(e,"api"),mn(e),Qn(e)];e.httpAdmin.get("/mcp/api/v1/links",...L,O(Il(e,n.linkStore))),e.httpAdmin.post("/mcp/api/v1/links",...L,O(Ll(e,n.linkStore,n.auditLog))),e.httpAdmin.delete("/mcp/api/v1/links/:id",...L,O(Nl(e,n.linkStore,n.auditLog)));let D=[i,d,mn(e),sa(e,v=>n.linkStore.validateResult(v))],H=n.flowAdapter,T=n.nodeTypesAdapter,k={nodeTypes:T,...n.catalogueFetcher?{catalogueFetcher:n.catalogueFetcher}:{},...n.catalogueUrl?{catalogueUrl:n.catalogueUrl}:{}};e.httpAdmin.get("/mcp/api/v1/flows",...D,O(qd(H))),e.httpAdmin.get("/mcp/api/v1/flows/:id",...D,O(zo(H))),e.httpAdmin.get("/mcp/api/v1/nodes/:id",...D,O(Wo(H))),e.httpAdmin.get("/mcp/api/v1/nodes/:id/field/:name",...D,O(Go(H))),e.httpAdmin.get("/mcp/api/v1/search",...D,O(Ko(H))),e.httpAdmin.get("/mcp/api/v1/node-types",...D,O(Jo(k))),e.httpAdmin.get("/mcp/api/v1/node-types/:type",...D,O(Vo(k))),e.httpAdmin.get("/mcp/api/v1/palette/search",...D,O(Yo(k)));let _=e.settings.httpAdminRoot;e.httpAdmin.get("/mcp/api/v1/proposals",...D,O(gc(n.proposalStore,n.flowAdapter,_))),e.httpAdmin.get("/mcp/api/v1/proposals/:id",...D,O(yc(n.proposalStore,_,n.flowAdapter))),e.httpAdmin.get("/mcp/api/v1/proposals/:id/diff",...D,O(kc(n.proposalStore,n.flowAdapter))),e.httpAdmin.post("/mcp/api/v1/proposals",i,d,De("rest"),Dt(e,n.linkStore,"rest"),O(mc(n.proposalStore,n.auditLog,n.flowAdapter,_,n.nodeTypesAdapter))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/approve",...L,De("rest"),O(wc(n.proposalService,_))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/reject",...L,De("rest"),O(bc(n.proposalService,_))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/withdraw",i,d,De("rest"),Dt(e,n.linkStore,"rest"),O(vc(n.proposalStore,n.auditLog,_))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/resolve-conflict",...L,De("rest"),O(Sc(n.proposalStore,n.auditLog,H,_))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/recheck",...L,O(xc(n.proposalStore,H,_))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/resolve",i,d,De("rest"),Dt(e,n.linkStore,"rest"),O(Ac(n.proposalStore,n.auditLog,H,_,T)));let E=[i,d,(v,S,N)=>{let w=v.headers.authorization;if(!n.settingsStore.getEffective().oauth.enabled||w?.toLowerCase().startsWith("bearer aia_")){if(n.settingsStore.getEffective().oauth.enabled){let F=S.setHeader.bind(S);S.setHeader=((I,C)=>{if(I.toLowerCase()==="www-authenticate"&&typeof C=="string"&&!C.includes("resource_metadata=")){let ee=`${c().replace(/\/$/,"")}${m.httpNodeRootFallback?"/mcp":""}/.well-known/oauth-protected-resource/mcp`;C+=`, resource_metadata="${ee}", scope="read propose"`}return F(I,C)})}Dt(e,n.linkStore,"mcp")(v,S,N);return}mn(e)(v,S,()=>f.auth(v,S,N))}];e.httpAdmin.post("/mcp/rpc",...E,De("mcp"),O(Ni(e,n))),e.httpAdmin.get("/mcp/rpc",...E,Cr),e.httpAdmin.delete("/mcp/rpc",...E,Cr),e.httpAdmin.post("/mcp",...E,De("mcp"),O(Ni(e,n))),e.httpAdmin.get("/mcp",...E,Cr),e.httpAdmin.delete("/mcp",...E,Cr),e.httpAdmin.get("/mcp/api/v1/logs",i,d,Ie(e,"api"),mn(e),O(Fl(n.auditLog,n.linkStore))),e.httpAdmin.use("/mcp/api/v1",jd()),Rl(e)}a(Kc,"registerRoutes");function Oy(e){e.plugins.registerPlugin(Ly,{type:"com55-ai-access",onremove(){ps.get(e)?.(),ps.delete(e)},onadd(){Kc(e),e.log?.info?.("Node-RED AI Access plugin loaded (dashboard /ai-access, MCP /mcp)")}})}a(Oy,"registerAiAccessPlugin");var $y=Oy;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});
|