aws-runtime-bridge 1.9.122 → 1.9.124

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.
@@ -1,6 +1,6 @@
1
- import L from"node:crypto";import A from"node:fs";import l from"node:path";import{Router as N}from"express";import*as D from"node-pty";import{v4 as M}from"uuid";import U,{WebSocketServer as z}from"ws";import{isRuntimeTokenValid as x,validateToken as h}from"../middleware/auth.js";import{createLogger as R}from"../utils/logger.js";const p=R("pty"),j=80,W=24,_=2e5,E=30*60*1e3,$=5*60*1e3,v=60*1e3,b=8,F=new Set(["false","halt","nologin","reboot","shutdown","sync","true"]),c=new Map,w=new Map;function B(e=process.env){const t=String(e.AWS_PTY_IDLE_TTL_MS||"").trim();if(!t)return E;const r=Number(t);return!Number.isFinite(r)||r<1e3?E:r}function Y(e=process.platform,t=process.env,r=A.existsSync){return e==="win32"?{shell:String(t.ComSpec||t.COMSPEC||"").trim()||"powershell.exe",args:[]}:{shell:[String(t.SHELL||"").trim(),"/bin/bash","/bin/sh"].find(o=>X(o,r))||"/bin/sh",args:[]}}function X(e,t){const r=e.trim();if(!r)return!1;const n=l.basename(r).toLowerCase();return F.has(n)?!1:!l.isAbsolute(r)||t(r)}function H(e,t=process.platform,r=process.env){const n=String(e||"auto").trim();if(!n||n==="auto")return Y(t,r);const i=new Set(["bash","sh","zsh","fish","pwsh","pwsh.exe","powershell","powershell.exe","cmd","cmd.exe"]),o=(t==="win32"?l.win32.basename(n):l.basename(n)).toLowerCase();if(!i.has(o))throw new Error(`shell is not allowed: ${n}`);return{shell:/^[A-Za-z]:[\\/]/.test(n)||n.startsWith("\\")||n.startsWith("/")?n:t==="win32"?{cmd:"cmd.exe",powershell:"powershell.exe",pwsh:"pwsh.exe"}[o]??n:n,args:[]}}function J(e,t){const r=l.relative(l.resolve(t),l.resolve(e));return r===""||!r.startsWith("..")&&!l.isAbsolute(r)}function V(e=process.env){return String(e.AWS_PTY_ALLOWED_ROOTS||"").split(l.delimiter).map(t=>t.trim()).filter(Boolean).map(t=>l.resolve(t))}function K(e,t=process.env){const r=String(e||"").trim();if(!r)throw new Error("workspacePath is required");if(!l.isAbsolute(r))throw new Error("workspacePath must be absolute");const n=l.resolve(r);if(!A.statSync(n).isDirectory())throw new Error("workspacePath must be an existing directory");const o=V(t);if(o.length>0&&!o.some(a=>J(n,a)))throw new Error("workspacePath is outside allowed roots");return n}function T(e,t){const r=Number(e);return!Number.isInteger(r)||r<=0?t:Math.min(r,500)}function q(e){return{...e,TERM:e.TERM||"xterm-256color",COLORTERM:e.COLORTERM||"truecolor"}}function Z(e,t){e.outputBuffer=`${e.outputBuffer}${t}`,e.outputBuffer.length>_&&(e.outputBuffer=e.outputBuffer.slice(-_))}function g(e){return{id:e.id,title:e.title,workspacePath:e.workspacePath,shell:e.shell,cols:e.cols,rows:e.rows,status:e.status,createdAt:e.createdAt,lastActiveAt:e.lastActiveAt,exitCode:e.exitCode,signal:e.signal,attachedClients:e.sockets.size,ownerUserId:e.ownerUserId,tenantId:e.tenantId,projectId:e.projectId}}function k(e,t){e.readyState===U.OPEN&&e.send(JSON.stringify(t))}function I(e,t){for(const r of e.sockets)k(r,t)}function P(e){e.idleTimer&&clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>{e.sockets.size===0&&S(e.id,"idle-timeout")},B())}function C(e){e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null)}function G(e){if(c.size>=re())throw new Error("maximum PTY sessions reached");const t=K(e.workspacePath),r=H(e.shell),n=T(e.cols,j),i=T(e.rows,W),o=`pty_${M()}`,a=new Date().toISOString(),d=String(e.title||"").trim()||`Terminal ${c.size+1}`,u=D.spawn(r.shell,r.args,{name:"xterm-256color",cols:n,rows:i,cwd:t,env:q(process.env)}),s={id:o,title:d,workspacePath:t,shell:r.shell,cols:n,rows:i,status:"running",createdAt:a,lastActiveAt:a,attachedClients:0,ptyProcess:u,sockets:new Set,outputBuffer:"",idleTimer:null,persistent:e.persistent===!0,ownerUserId:String(e.ownerUserId||"").trim()||void 0,tenantId:String(e.tenantId||"").trim()||void 0,projectId:String(e.projectId||"").trim()||void 0};return u.onData(f=>{s.lastActiveAt=new Date().toISOString(),Z(s,f),I(s,{type:"output",data:f})}),u.onExit(({exitCode:f,signal:m})=>{s.status="exited",s.exitCode=f,s.signal=m,s.lastActiveAt=new Date().toISOString(),I(s,{type:"exit",exitCode:f,signal:m}),s.sockets.size===0&&!s.persistent?P(s):s.persistent&&(s.idleTimer&&clearTimeout(s.idleTimer),s.idleTimer=setTimeout(()=>{S(s.id,"exited-timeout")},te()))}),c.set(o,s),s.persistent||P(s),Q(s,e.preCommand),p.info(`created pty session id=${o}, cwd=${t}, shell=${r.shell}`),g(s)}function Q(e,t){const r=typeof t=="string"?t:"";if(!r.trim())return;const n=`${r.replace(/\n/g,"\r").replace(/\r+$/g,"")}\r`;try{e.ptyProcess.write(n)}catch(i){const o=i;p.warn(`failed to inject pty pre-command sessionId=${e.id}: ${o.message}`)}}function ee(e=process.env){const t=String(e.AWS_PTY_CONNECT_TOKEN_TTL_MS||"").trim();if(!t)return v;const r=Number(t);return!Number.isFinite(r)||r<5e3||r>10*60*1e3?v:r}function te(e=process.env){const t=String(e.AWS_PTY_EXITED_TTL_MS||"").trim();if(!t)return $;const r=Number(t);return!Number.isFinite(r)||r<1e3?$:r}function re(e=process.env){const t=String(e.AWS_PTY_MAX_SESSIONS||"").trim();if(!t)return b;const r=Number(t);return!Number.isInteger(r)||r<1||r>100?b:r}function ne(e){if(!e||typeof e!="object")return{workspacePath:void 0};const t=e;return{workspacePath:t.workspacePath,title:t.title,shell:t.shell,cols:t.cols,rows:t.rows,persistent:t.persistent,preCommand:t.preCommand,ownerUserId:t.ownerUserId,tenantId:t.tenantId,projectId:t.projectId}}function se(){return Array.from(c.values()).map(g)}function oe(e){const t=c.get(e);return t?g(t):void 0}function ie(e){if(!c.has(e))return;const t=`ptyws_${L.randomBytes(32).toString("base64url")}`,r=Date.now()+ee();return w.set(t,{sessionId:e,expiresAt:r}),{token:t,expiresAt:new Date(r).toISOString()}}function ce(e,t){const r=w.get(t);return r?r.expiresAt<Date.now()?(w.delete(t),p.warn(`[pty-ws] consumePtyConnectToken: token \u5DF2\u8FC7\u671F, sessionId=${e}, expiredAt=${new Date(r.expiresAt).toISOString()}`),!1):r.sessionId!==e?(p.warn(`[pty-ws] consumePtyConnectToken: sessionId \u4E0D\u5339\u914D, expected=${r.sessionId}, actual=${e}`),!1):(w.delete(t),p.info(`[pty-ws] consumePtyConnectToken: \u6210\u529F, sessionId=${e}`),!0):(p.warn(`[pty-ws] consumePtyConnectToken: token \u4E0D\u5B58\u5728, sessionId=${e}, ptyConnectTokens.size=${w.size}`),!1)}function ae(e){for(const[t,r]of w.entries())r.sessionId===e&&w.delete(t)}function S(e,t="closed"){const r=c.get(e);if(!r)return!1;C(r),ae(e),I(r,{type:"status",status:"closing",reason:t});for(const n of r.sockets)n.close(1e3,t);return r.sockets.clear(),r.status==="running"&&r.ptyProcess.kill(),c.delete(e),p.info(`closed pty session id=${e}, reason=${t}`),!0}function ge(e="shutdown"){for(const t of Array.from(c.keys()))S(t,e)}const y=N();y.get("/sessions",h,(e,t)=>{t.json({ok:!0,sessions:se()})}),y.post("/sessions",h,(e,t)=>{try{const r=G(ne(e.body));t.status(201).json({ok:!0,session:r})}catch(r){const n=r;t.status(400).json({error:n.message})}}),y.get("/sessions/:sessionId",h,(e,t)=>{const r=oe(e.params.sessionId);if(!r){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0,session:r})}),y.delete("/sessions/:sessionId",h,(e,t)=>{if(!S(e.params.sessionId,"explicit-close")){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0})}),y.post("/sessions/:sessionId/connect-token",h,(e,t)=>{const r=ie(e.params.sessionId);if(!r){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0,...r})});function ue(e){const t=e.toString();try{return JSON.parse(t)}catch{return{type:"input",data:t}}}function le(e,t){C(e),e.sockets.add(t),e.lastActiveAt=new Date().toISOString(),k(t,{type:"status",status:e.status,session:g(e)}),e.outputBuffer&&k(t,{type:"output",data:e.outputBuffer,replay:!0}),t.on("message",r=>{const n=ue(r);if(!n||typeof n!="object")return;const i=n,o=String(i.type||"");if(e.lastActiveAt=new Date().toISOString(),o==="input"){e.ptyProcess.write(String(i.data||""));return}if(o==="resize"){const a=T(i.cols,e.cols),d=T(i.rows,e.rows);e.cols=a,e.rows=d,e.ptyProcess.resize(a,d);return}if(o==="close"){S(e.id,"client-close");return}o==="ping"&&k(t,{type:"pong"})}),t.on("close",()=>{e.sockets.delete(t),e.lastActiveAt=new Date().toISOString(),e.sockets.size===0&&e.status==="running"&&!e.persistent&&P(e)})}function pe(e,t){const r=t.searchParams.get("token")||"";if(r.trim())return r.trim();const n=e.headers["x-runtime-token"];return Array.isArray(n)?n[0]?.trim()||"":typeof n=="string"?n.trim():String(e.headers.authorization||"").match(/^Bearer\s+(.+)$/i)?.[1]?.trim()||""}function ke(e){const t=new z({noServer:!0});e.on("upgrade",(r,n,i)=>{const o=r.headers.host||"localhost",a=new URL(r.url||"/",`http://${o}`),d=a.pathname.match(/^\/pty\/sessions\/([^/]+)\/stream$/);if(!d)return;const u=pe(r,a),s=decodeURIComponent(d[1]||""),f=u?u.length>12?u.substring(0,12)+"...":u:"empty";if(p.info(`[pty-ws] upgrade \u8BF7\u6C42: sessionId=${s}, token=${f}, pathname=${a.pathname}`),!x(u)&&!ce(s,u)){p.warn(`[pty-ws] token \u6821\u9A8C\u5931\u8D25: sessionId=${s}, token=${f}, runtimeTokenValid=${x(u)}, ptySessions.size=${c.size}`),n.write(`HTTP/1.1 401 Unauthorized\r
1
+ import L from"node:crypto";import A from"node:fs";import u from"node:path";import{Router as N}from"express";import*as D from"node-pty";import{v4 as M}from"uuid";import U,{WebSocketServer as z}from"ws";import{isRuntimeTokenValid as x,validateToken as h}from"../middleware/auth.js";import{isUpgradeAllowed as R}from"../services/access-control.js";import{createLogger as j}from"../utils/logger.js";const p=j("pty"),W=80,F=24,_=2e5,E=30*60*1e3,$=5*60*1e3,v=60*1e3,b=8,B=new Set(["false","halt","nologin","reboot","shutdown","sync","true"]),c=new Map,w=new Map;function Y(e=process.env){const t=String(e.AWS_PTY_IDLE_TTL_MS||"").trim();if(!t)return E;const r=Number(t);return!Number.isFinite(r)||r<1e3?E:r}function X(e=process.platform,t=process.env,r=A.existsSync){return e==="win32"?{shell:String(t.ComSpec||t.COMSPEC||"").trim()||"powershell.exe",args:[]}:{shell:[String(t.SHELL||"").trim(),"/bin/bash","/bin/sh"].find(s=>H(s,r))||"/bin/sh",args:[]}}function H(e,t){const r=e.trim();if(!r)return!1;const n=u.basename(r).toLowerCase();return B.has(n)?!1:!u.isAbsolute(r)||t(r)}function J(e,t=process.platform,r=process.env){const n=String(e||"auto").trim();if(!n||n==="auto")return X(t,r);const i=new Set(["bash","sh","zsh","fish","pwsh","pwsh.exe","powershell","powershell.exe","cmd","cmd.exe"]),s=(t==="win32"?u.win32.basename(n):u.basename(n)).toLowerCase();if(!i.has(s))throw new Error(`shell is not allowed: ${n}`);return{shell:/^[A-Za-z]:[\\/]/.test(n)||n.startsWith("\\")||n.startsWith("/")?n:t==="win32"?{cmd:"cmd.exe",powershell:"powershell.exe",pwsh:"pwsh.exe"}[s]??n:n,args:[]}}function V(e,t){const r=u.relative(u.resolve(t),u.resolve(e));return r===""||!r.startsWith("..")&&!u.isAbsolute(r)}function K(e=process.env){return String(e.AWS_PTY_ALLOWED_ROOTS||"").split(u.delimiter).map(t=>t.trim()).filter(Boolean).map(t=>u.resolve(t))}function Z(e,t=process.env){const r=String(e||"").trim();if(!r)throw new Error("workspacePath is required");if(!u.isAbsolute(r))throw new Error("workspacePath must be absolute");const n=u.resolve(r);if(!A.statSync(n).isDirectory())throw new Error("workspacePath must be an existing directory");const s=K(t);if(s.length>0&&!s.some(a=>V(n,a)))throw new Error("workspacePath is outside allowed roots");return n}function T(e,t){const r=Number(e);return!Number.isInteger(r)||r<=0?t:Math.min(r,500)}function q(e){return{...e,TERM:e.TERM||"xterm-256color",COLORTERM:e.COLORTERM||"truecolor"}}function G(e,t){e.outputBuffer=`${e.outputBuffer}${t}`,e.outputBuffer.length>_&&(e.outputBuffer=e.outputBuffer.slice(-_))}function g(e){return{id:e.id,title:e.title,workspacePath:e.workspacePath,shell:e.shell,cols:e.cols,rows:e.rows,status:e.status,createdAt:e.createdAt,lastActiveAt:e.lastActiveAt,exitCode:e.exitCode,signal:e.signal,attachedClients:e.sockets.size,ownerUserId:e.ownerUserId,tenantId:e.tenantId,projectId:e.projectId}}function I(e,t){e.readyState===U.OPEN&&e.send(JSON.stringify(t))}function P(e,t){for(const r of e.sockets)I(r,t)}function k(e){e.idleTimer&&clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>{e.sockets.size===0&&S(e.id,"idle-timeout")},Y())}function C(e){e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null)}function Q(e){if(c.size>=ne())throw new Error("maximum PTY sessions reached");const t=Z(e.workspacePath),r=J(e.shell),n=T(e.cols,W),i=T(e.rows,F),s=`pty_${M()}`,a=new Date().toISOString(),d=String(e.title||"").trim()||`Terminal ${c.size+1}`,l=D.spawn(r.shell,r.args,{name:"xterm-256color",cols:n,rows:i,cwd:t,env:q(process.env)}),o={id:s,title:d,workspacePath:t,shell:r.shell,cols:n,rows:i,status:"running",createdAt:a,lastActiveAt:a,attachedClients:0,ptyProcess:l,sockets:new Set,outputBuffer:"",idleTimer:null,persistent:e.persistent===!0,ownerUserId:String(e.ownerUserId||"").trim()||void 0,tenantId:String(e.tenantId||"").trim()||void 0,projectId:String(e.projectId||"").trim()||void 0};return l.onData(f=>{o.lastActiveAt=new Date().toISOString(),G(o,f),P(o,{type:"output",data:f})}),l.onExit(({exitCode:f,signal:m})=>{o.status="exited",o.exitCode=f,o.signal=m,o.lastActiveAt=new Date().toISOString(),P(o,{type:"exit",exitCode:f,signal:m}),o.sockets.size===0&&!o.persistent?k(o):o.persistent&&(o.idleTimer&&clearTimeout(o.idleTimer),o.idleTimer=setTimeout(()=>{S(o.id,"exited-timeout")},re()))}),c.set(s,o),o.persistent||k(o),ee(o,e.preCommand),p.info(`created pty session id=${s}, cwd=${t}, shell=${r.shell}`),g(o)}function ee(e,t){const r=typeof t=="string"?t:"";if(!r.trim())return;const n=`${r.replace(/\n/g,"\r").replace(/\r+$/g,"")}\r`;try{e.ptyProcess.write(n)}catch(i){const s=i;p.warn(`failed to inject pty pre-command sessionId=${e.id}: ${s.message}`)}}function te(e=process.env){const t=String(e.AWS_PTY_CONNECT_TOKEN_TTL_MS||"").trim();if(!t)return v;const r=Number(t);return!Number.isFinite(r)||r<5e3||r>10*60*1e3?v:r}function re(e=process.env){const t=String(e.AWS_PTY_EXITED_TTL_MS||"").trim();if(!t)return $;const r=Number(t);return!Number.isFinite(r)||r<1e3?$:r}function ne(e=process.env){const t=String(e.AWS_PTY_MAX_SESSIONS||"").trim();if(!t)return b;const r=Number(t);return!Number.isInteger(r)||r<1||r>100?b:r}function oe(e){if(!e||typeof e!="object")return{workspacePath:void 0};const t=e;return{workspacePath:t.workspacePath,title:t.title,shell:t.shell,cols:t.cols,rows:t.rows,persistent:t.persistent,preCommand:t.preCommand,ownerUserId:t.ownerUserId,tenantId:t.tenantId,projectId:t.projectId}}function se(){return Array.from(c.values()).map(g)}function ie(e){const t=c.get(e);return t?g(t):void 0}function ce(e){if(!c.has(e))return;const t=`ptyws_${L.randomBytes(32).toString("base64url")}`,r=Date.now()+te();return w.set(t,{sessionId:e,expiresAt:r}),{token:t,expiresAt:new Date(r).toISOString()}}function ae(e,t){const r=w.get(t);return r?r.expiresAt<Date.now()?(w.delete(t),p.warn(`[pty-ws] consumePtyConnectToken: token \u5DF2\u8FC7\u671F, sessionId=${e}, expiredAt=${new Date(r.expiresAt).toISOString()}`),!1):r.sessionId!==e?(p.warn(`[pty-ws] consumePtyConnectToken: sessionId \u4E0D\u5339\u914D, expected=${r.sessionId}, actual=${e}`),!1):(w.delete(t),p.info(`[pty-ws] consumePtyConnectToken: \u6210\u529F, sessionId=${e}`),!0):(p.warn(`[pty-ws] consumePtyConnectToken: token \u4E0D\u5B58\u5728, sessionId=${e}, ptyConnectTokens.size=${w.size}`),!1)}function le(e){for(const[t,r]of w.entries())r.sessionId===e&&w.delete(t)}function S(e,t="closed"){const r=c.get(e);if(!r)return!1;C(r),le(e),P(r,{type:"status",status:"closing",reason:t});for(const n of r.sockets)n.close(1e3,t);return r.sockets.clear(),r.status==="running"&&r.ptyProcess.kill(),c.delete(e),p.info(`closed pty session id=${e}, reason=${t}`),!0}function Pe(e="shutdown"){for(const t of Array.from(c.keys()))S(t,e)}const y=N();y.get("/sessions",h,(e,t)=>{t.json({ok:!0,sessions:se()})}),y.post("/sessions",h,(e,t)=>{try{const r=Q(oe(e.body));t.status(201).json({ok:!0,session:r})}catch(r){const n=r;t.status(400).json({error:n.message})}}),y.get("/sessions/:sessionId",h,(e,t)=>{const r=ie(e.params.sessionId);if(!r){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0,session:r})}),y.delete("/sessions/:sessionId",h,(e,t)=>{if(!S(e.params.sessionId,"explicit-close")){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0})}),y.post("/sessions/:sessionId/connect-token",h,(e,t)=>{const r=ce(e.params.sessionId);if(!r){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0,...r})});function ue(e){const t=e.toString();try{return JSON.parse(t)}catch{return{type:"input",data:t}}}function pe(e,t){C(e),e.sockets.add(t),e.lastActiveAt=new Date().toISOString(),I(t,{type:"status",status:e.status,session:g(e)}),e.outputBuffer&&I(t,{type:"output",data:e.outputBuffer,replay:!0}),t.on("message",r=>{const n=ue(r);if(!n||typeof n!="object")return;const i=n,s=String(i.type||"");if(e.lastActiveAt=new Date().toISOString(),s==="input"){e.ptyProcess.write(String(i.data||""));return}if(s==="resize"){const a=T(i.cols,e.cols),d=T(i.rows,e.rows);e.cols=a,e.rows=d,e.ptyProcess.resize(a,d);return}if(s==="close"){S(e.id,"client-close");return}s==="ping"&&I(t,{type:"pong"})}),t.on("close",()=>{e.sockets.delete(t),e.lastActiveAt=new Date().toISOString(),e.sockets.size===0&&e.status==="running"&&!e.persistent&&k(e)})}function de(e,t){const r=t.searchParams.get("token")||"";if(r.trim())return r.trim();const n=e.headers["x-runtime-token"];return Array.isArray(n)?n[0]?.trim()||"":typeof n=="string"?n.trim():String(e.headers.authorization||"").match(/^Bearer\s+(.+)$/i)?.[1]?.trim()||""}function ke(e){const t=new z({noServer:!0});e.on("upgrade",(r,n,i)=>{if(!R(r,n))return;const s=r.headers.host||"localhost",a=new URL(r.url||"/",`http://${s}`),d=a.pathname.match(/^\/pty\/sessions\/([^/]+)\/stream$/);if(!d)return;const l=de(r,a),o=decodeURIComponent(d[1]||""),f=l?l.length>12?l.substring(0,12)+"...":l:"empty";if(p.info(`[pty-ws] upgrade \u8BF7\u6C42: sessionId=${o}, token=${f}, pathname=${a.pathname}`),!x(l)&&!ae(o,l)){p.warn(`[pty-ws] token \u6821\u9A8C\u5931\u8D25: sessionId=${o}, token=${f}, runtimeTokenValid=${x(l)}, ptySessions.size=${c.size}`),n.write(`HTTP/1.1 401 Unauthorized\r
2
2
  \r
3
- `),n.destroy();return}const m=c.get(s);if(!m){p.warn(`[pty-ws] session \u4E0D\u5B58\u5728: sessionId=${s}, ptySessions.size=${c.size}, keys=${Array.from(c.keys()).join(",")}`),n.write(`HTTP/1.1 404 Not Found\r
3
+ `),n.destroy();return}const m=c.get(o);if(!m){p.warn(`[pty-ws] session \u4E0D\u5B58\u5728: sessionId=${o}, ptySessions.size=${c.size}, keys=${Array.from(c.keys()).join(",")}`),n.write(`HTTP/1.1 404 Not Found\r
4
4
  \r
5
- `),n.destroy();return}p.info(`[pty-ws] handleUpgrade: sessionId=${s}, status=${m.status}, persistent=${m.persistent}, sockets=${m.sockets.size}`),t.handleUpgrade(r,n,i,O=>{le(m,O)})})}export{ke as attachPtyWebSocketServer,ge as closeAllPtySessions,S as closePtySession,ie as createPtyConnectToken,G as createPtySession,oe as getPtySessionSummary,J as isPathInsideRoot,se as listPtySessions,y as ptyRouter,c as ptySessions,Y as resolveDefaultShell,re as resolveMaxPtySessions,te as resolvePtyExitedTtlMs,B as resolvePtyIdleTtlMs,K as resolvePtyWorkspacePath,H as resolveRequestedShell};
5
+ `),n.destroy();return}p.info(`[pty-ws] handleUpgrade: sessionId=${o}, status=${m.status}, persistent=${m.persistent}, sockets=${m.sockets.size}`),t.handleUpgrade(r,n,i,O=>{pe(m,O)})})}export{ke as attachPtyWebSocketServer,Pe as closeAllPtySessions,S as closePtySession,ce as createPtyConnectToken,Q as createPtySession,ie as getPtySessionSummary,V as isPathInsideRoot,se as listPtySessions,y as ptyRouter,c as ptySessions,X as resolveDefaultShell,ne as resolveMaxPtySessions,re as resolvePtyExitedTtlMs,Y as resolvePtyIdleTtlMs,Z as resolvePtyWorkspacePath,J as resolveRequestedShell};
6
6
 
@@ -1,2 +1,2 @@
1
- import b from"axios";import{Router as T}from"express";import{getConfiguredConnectionKeys as I}from"../services/auto-register.js";import{validateToken as f}from"../middleware/auth.js";import{claimMcpLaunchBinding as j,getLatestAgentToken as R,getMcpLaunchQueueSize as k}from"../services/mcp-launch-binding-queue.js";import{clearRuntimeBinding as y,getRuntimeAccessToken as B,getRuntimeBindingPublicState as g,getRuntimePairingCode as S,hasRuntimeBinding as v,saveRuntimeBinding as p,validateRuntimeBindingToken as w,validateRuntimePairingCode as A}from"../services/runtime-binding.js";import{createLogger as U}from"../utils/logger.js";const a=U("runtime-binding-route"),d=T();function $(n){return{success:!1,error:"MCP runtime token must be claimed from launch binding; bridge-scoped token refresh is disabled"}}function q(n){const e=n.header("X-Runtime-Token");return e?e.trim():(n.header("Authorization")||"").match(/^Bearer\s+(.+)$/i)?.[1]?.trim()||""}d.get("/binding/status",(n,e)=>{const i=g();e.json({ok:!0,...i,pairingRequired:!i.paired})}),d.post("/binding/pair",(n,e)=>{const{accessToken:i,instanceId:t,userId:o,schedulerBaseUrl:r,pairingCode:s}=n.body||{},u=v(),c=q(n);if(u){if(!w(c)){e.status(401).json({error:"unauthorized",message:"Bridge is already paired. Provide the current runtime token to rotate binding."});return}}else if(!A(s)){e.status(401).json({error:"invalid_pairing_code",message:"Invalid pairing code. Read the current pairing code from the bridge console and try again."});return}try{p({accessToken:String(i||""),instanceId:t?String(t):void 0,userId:o?String(o):void 0,schedulerBaseUrl:r?String(r):void 0}),a.info(`[runtime-bridge] paired with instanceId=${t||"unknown"}`),e.json({ok:!0,...g()})}catch(l){const h=l;e.status(400).json({error:h.message||"pair runtime bridge failed"})}});function m(n){const e=n.ip||n.socket.remoteAddress||"";return e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1"}d.post("/binding/request-token",async(n,e)=>{if(!m(n)){e.status(403).json({ok:!1,error:"loopback request required"});return}const i=$(n.body||{});if(!i.success||!i.runtimeAccessToken){e.status(400).json({ok:!1,error:i.error||"request runtime token failed"});return}e.json({ok:!0,runtimeAccessToken:i.runtimeAccessToken,updated:i.updated})}),d.post("/binding/claim-agent",(n,e)=>{if(!m(n)){e.status(403).json({ok:!1,error:"loopback request required"});return}const{agentId:i,workspacePath:t,serverUrl:o}=n.body||{};if(!t||!String(t).trim()){e.status(400).json({ok:!1,error:"workspacePath is required"});return}if(!o||!String(o).trim()){e.status(400).json({ok:!1,error:"serverUrl is required"});return}const r=j({agentId:i,workspacePath:t,serverUrl:o});if(!r){e.status(404).json({ok:!1,error:"launch binding not found for workspacePath and serverUrl",queueSize:k(t)});return}C(r.agentId,r.schedulerBaseUrl).catch(()=>{}),e.json({ok:!0,agentId:r.agentId,workspacePath:r.workspacePath,runtimeAccessToken:r.runtimeAccessToken,userId:r.userId,schedulerBaseUrl:r.schedulerBaseUrl,serverUrl:r.serverUrl,mcpHttpUrl:r.mcpHttpUrl,bindingId:r.id,remainingQueueSize:k(t)})}),d.post("/binding/refresh-token",(n,e)=>{if(!m(n)){e.status(403).json({ok:!1,error:"loopback request required"});return}const{agentId:i,currentToken:t,forceRefresh:o}=n.body||{},r=String(i||"").trim();if(!r){e.status(400).json({ok:!1,error:"agentId is required"});return}const s=R(r);if(!s){e.status(404).json({ok:!1,error:"no cached token for agent"});return}const u=String(t||"").trim();if(!o&&u&&s.runtimeAccessToken===u){e.status(409).json({ok:!1,error:"token_unchanged",message:"Bridge cached token is the same as current; token may be revoked server-side"});return}e.json({ok:!0,runtimeAccessToken:s.runtimeAccessToken})}),d.post("/binding/issue",f,(n,e)=>{const{accessToken:i,instanceId:t,userId:o,schedulerBaseUrl:r}=n.body||{},s=String(i||"").trim();if(!s){e.status(400).json({error:"accessToken is required"});return}try{const c=B()!==s;c?(p({accessToken:s,instanceId:t?String(t):void 0,userId:o?String(o):void 0,schedulerBaseUrl:r?String(r):void 0}),a.info(`[runtime-bridge] runtime token redispatched and updated: instanceId=${t||"unknown"}`)):a.info(`[runtime-bridge] runtime token redispatched but unchanged: instanceId=${t||"unknown"}`),e.json({ok:!0,updated:c,...g()})}catch(u){const c=u;e.status(400).json({error:c.message||"issue runtime token failed"})}}),d.post("/binding/unpair",f,(n,e)=>{y(),e.json({ok:!0,...g()})});function H(){const n=g();if(n.paired){a.info(`[runtime-bridge] runtime binding loaded: instanceId=${n.instanceId||"unknown"}`);return}a.info("[runtime-bridge] bridge is unpaired; sensitive APIs will reject requests until paired"),a.info(`[runtime-bridge] pairing code: ${S()}`)}async function C(n,e){if(!e){a.debug(`[runtime-binding] notifySchedulerBindingClaimed skipped: no schedulerBaseUrl for agentId=${n}`);return}const t=I()[0];if(!t){a.warn(`[runtime-binding] notifySchedulerBindingClaimed skipped: no connection key for agentId=${n}`);return}const o=`${e.replace(/\/+$/,"")}/api/runtime/bridge/binding-claimed`;try{await b.post(o,{agentId:n},{headers:{"X-Runtime-Token":t,"Content-Type":"application/json"},timeout:5e3}),a.debug(`[runtime-binding] Notified scheduler: agentId=${n}, url=${o}`)}catch(r){const s=r instanceof Error?r.message:String(r);a.warn(`[runtime-binding] Failed to notify scheduler binding claimed: agentId=${n}, error=${s}`)}}export{H as logRuntimeBindingStartupState,$ as resolveRuntimeTokenRequest,d as runtimeBindingRouter};
1
+ import T from"axios";import{Router as I}from"express";import{getConfiguredConnectionKeys as j}from"../services/auto-register.js";import{validateToken as f}from"../middleware/auth.js";import{claimMcpLaunchBinding as R,getLatestAgentToken as S,getMcpLaunchQueueSize as k}from"../services/mcp-launch-binding-queue.js";import{clearRuntimeBinding as B,getRuntimeAccessToken as p,getRuntimeBindingPublicState as g,getRuntimePairingCode as y,hasRuntimeBinding as v,saveRuntimeBinding as h,validateRuntimeBindingToken as w,validateRuntimePairingCode as A}from"../services/runtime-binding.js";import{createLogger as $}from"../utils/logger.js";const s=$("runtime-binding-route"),d=I();function q(n){return{success:!1,error:"MCP runtime token must be claimed from launch binding; bridge-scoped token refresh is disabled"}}function U(n){const e=n.header("X-Runtime-Token");return e?e.trim():(n.header("Authorization")||"").match(/^Bearer\s+(.+)$/i)?.[1]?.trim()||""}d.get("/binding/status",(n,e)=>{const t=g();e.json({ok:!0,...t,pairingRequired:!t.paired})}),d.post("/binding/pair",(n,e)=>{const{accessToken:t,instanceId:i,userId:o,schedulerBaseUrl:r,pairingCode:a}=n.body||{},u=v(),c=U(n);if(u){if(!w(c)){e.status(401).json({error:"unauthorized",message:"Bridge is already paired. Provide the current runtime token to rotate binding."});return}}else if(!A(a)){e.status(401).json({error:"invalid_pairing_code",message:"Invalid pairing code. Read the current pairing code from the bridge console and try again."});return}try{h({accessToken:String(t||""),instanceId:i?String(i):void 0,userId:o?String(o):void 0,schedulerBaseUrl:r?String(r):void 0}),s.info(`[runtime-bridge] paired with instanceId=${i||"unknown"}`),e.json({ok:!0,...g()})}catch(l){const b=l;e.status(400).json({error:b.message||"pair runtime bridge failed"})}});function m(n){const e=n.ip||n.socket.remoteAddress||"";return e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1"}d.post("/binding/request-token",async(n,e)=>{if(!m(n)){e.status(403).json({ok:!1,error:"loopback request required"});return}const t=q(n.body||{});if(!t.success||!t.runtimeAccessToken){e.status(400).json({ok:!1,error:t.error||"request runtime token failed"});return}e.json({ok:!0,runtimeAccessToken:t.runtimeAccessToken,updated:t.updated})}),d.post("/binding/claim-agent",(n,e)=>{if(!m(n)){e.status(403).json({ok:!1,error:"loopback request required"});return}const{agentId:t,workspacePath:i,serverUrl:o}=n.body||{};if(!i||!String(i).trim()){e.status(400).json({ok:!1,error:"workspacePath is required"});return}if(!o||!String(o).trim()){e.status(400).json({ok:!1,error:"serverUrl is required"});return}const r=R({agentId:t,workspacePath:i,serverUrl:o});if(!r){e.status(404).json({ok:!1,error:"launch binding not found for workspacePath and serverUrl",queueSize:k(i)});return}C(r.agentId,r.schedulerBaseUrl).catch(()=>{}),e.json({ok:!0,agentId:r.agentId,workspacePath:r.workspacePath,runtimeAccessToken:r.runtimeAccessToken,userId:r.userId,schedulerBaseUrl:r.schedulerBaseUrl,serverUrl:r.serverUrl,mcpHttpUrl:r.mcpHttpUrl,bindingId:r.id,remainingQueueSize:k(i)})}),d.post("/binding/refresh-token",(n,e)=>{if(!m(n)){e.status(403).json({ok:!1,error:"loopback request required"});return}const{agentId:t,currentToken:i,forceRefresh:o}=n.body||{},r=String(t||"").trim();if(!r){e.status(400).json({ok:!1,error:"agentId is required"});return}const a=S(r);if(!a){e.status(404).json({ok:!1,error:"no cached token for agent"});return}const u=String(i||"").trim();if(!o&&u&&a.runtimeAccessToken===u){e.status(409).json({ok:!1,error:"token_unchanged",message:"Bridge cached token is the same as current; token may be revoked server-side"});return}e.json({ok:!0,runtimeAccessToken:a.runtimeAccessToken})}),d.post("/binding/issue",f,(n,e)=>{const{accessToken:t,instanceId:i,userId:o,schedulerBaseUrl:r}=n.body||{},a=String(t||"").trim();if(!a){e.status(400).json({error:"accessToken is required"});return}try{const c=p()!==a;c?(h({accessToken:a,instanceId:i?String(i):void 0,userId:o?String(o):void 0,schedulerBaseUrl:r?String(r):void 0}),s.info(`[runtime-bridge] runtime token redispatched and updated: instanceId=${i||"unknown"}`)):s.info(`[runtime-bridge] runtime token redispatched but unchanged: instanceId=${i||"unknown"}`),e.json({ok:!0,updated:c,...g()})}catch(u){const c=u;e.status(400).json({error:c.message||"issue runtime token failed"})}}),d.post("/binding/unpair",f,(n,e)=>{B(),e.json({ok:!0,...g()})});function Q(){const n=g();if(n.paired){s.info(`[runtime-bridge] runtime binding loaded: instanceId=${n.instanceId||"unknown"}`);return}s.info("[runtime-bridge] bridge is unpaired; sensitive APIs will reject requests until paired"),s.info(`[runtime-bridge] pairing code: ${y()}`)}async function C(n,e){if(!e){s.debug(`[runtime-binding] notifySchedulerBindingClaimed skipped: no schedulerBaseUrl for agentId=${n}`);return}const t=p(e)||j()[0];if(!t){s.warn(`[runtime-binding] notifySchedulerBindingClaimed skipped: no connection key for agentId=${n}`);return}const i=`${e.replace(/\/+$/,"")}/api/runtime/bridge/binding-claimed`;try{await T.post(i,{agentId:n},{headers:{"X-Runtime-Token":t,"Content-Type":"application/json"},timeout:5e3}),s.debug(`[runtime-binding] Notified scheduler: agentId=${n}, url=${i}`)}catch(o){const r=o instanceof Error?o.message:String(o);s.warn(`[runtime-binding] Failed to notify scheduler binding claimed: agentId=${n}, error=${r}`)}}export{Q as logRuntimeBindingStartupState,q as resolveRuntimeTokenRequest,d as runtimeBindingRouter};
2
2
 
@@ -0,0 +1,18 @@
1
+
2
+ import type { IncomingMessage } from "node:http";
3
+ import type { Socket } from "node:net";
4
+ import type { NextFunction, Request, Response } from "express";
5
+
6
+ export declare function parseAllowIps(raw: unknown): string[];
7
+
8
+ export declare function isValidIpEntry(entry: string): boolean;
9
+
10
+ export declare function isIpAllowed(clientIp: string, entries: string[]): boolean;
11
+
12
+ export declare function getAccessControlEntries(): string[];
13
+
14
+ export declare function refreshAccessControlCache(): void;
15
+
16
+ export declare function accessControlMiddleware(req: Request, res: Response, next: NextFunction): void;
17
+
18
+ export declare function isUpgradeAllowed(request: IncomingMessage, socket: Socket): boolean;
@@ -0,0 +1,5 @@
1
+ import{existsSync as S,readFileSync as w,statSync as C}from"node:fs";import f from"node:net";import{getAutoRegisterConfigFilePath as $}from"./auto-register.js";import{createLogger as y}from"../utils/logger.js";const p=y("access-control"),I=new Set(["0.0.0.0","*"]);function P(n){const[t,r,e,s]=n.split(".").map(Number);return BigInt(t)<<24n|BigInt(r)<<16n|BigInt(e)<<8n|BigInt(s)}function b(n){let t=n;const r=t.lastIndexOf(":"),e=t.slice(r+1);if(e.includes(".")){if(!f.isIPv4(e))return null;t=`${t.slice(0,r+1)}0:0`}const s=(t.match(/::/g)||[]).length;if(s>1)return null;if(s===1){const l=t.indexOf("::"),d=t.slice(0,l)?t.slice(0,l).split(":").filter(Boolean):[],a=t.slice(l+2)?t.slice(l+2).split(":").filter(Boolean):[],g=8-d.length-a.length;if(g<1)return null;t=[...d,...Array(g).fill("0"),...a].join(":")}const i=t.split(":");if(i.length!==8)return null;let o=0n;for(const l of i){if(!/^[0-9a-fA-F]{1,4}$/.test(l))return null;o=o<<16n|BigInt(parseInt(l,16))}return o}function m(n){return f.isIPv4(n)?P(n):f.isIPv6(n)?b(n):null}function u(n){let t=String(n||"").trim();const r=t.indexOf("%");r!==-1&&(t=t.slice(0,r));const e=t.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i);return e&&f.isIPv4(e[1])?e[1]:t}function h(n){const t=String(n.socket?.remoteAddress||""),r=u(t);if(r==="127.0.0.1"||r==="::1"){const e=n.headers?.["x-forwarded-for"];if(typeof e=="string"&&e.trim()){const s=e.split(",").map(i=>u(i.trim())).filter(Boolean);for(let i=s.length-1;i>=0;i--){const o=s[i];if(o&&!x(o)&&m(o)!==null)return o}}}return t}function x(n){return n==="::1"||/^127\./.test(n)}function v(n,t,r){const e=n.lastIndexOf("/");let s=n.slice(0,e),i=Number(n.slice(e+1));const o=s.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i);if(o){if(r!==32||i<96)return!1;s=o[1],i=i-96}const l=m(s);if(l===null||!Number.isInteger(i)||i<0||i>r||(s.includes(":")?128:32)!==r)return!1;const a=BigInt(r-i);return t>>a===l>>a}function N(n){let t;if(typeof n=="string")t=n.split(/[\n,;]+/);else if(Array.isArray(n))t=n.map(s=>String(s));else return[];const r=new Set,e=[];for(const s of t){const i=s.trim();!i||r.has(i)||(r.add(i),e.push(i))}return e}function E(n){const t=String(n||"").trim();if(!t)return!1;if(I.has(t))return!0;const r=t.lastIndexOf("/");if(r===-1)return f.isIP(t)!==0;const e=t.slice(0,r),s=Number(t.slice(r+1));if(f.isIP(e)===0||!/^\d+$/.test(t.slice(r+1)))return!1;const i=e.includes(":")?128:32;return Number.isInteger(s)&&s>=0&&s<=i}function B(n,t){const r=(t||[]).map(o=>u(String(o||"").trim())).filter(Boolean);if(r.length===0||r.some(o=>I.has(o)))return!0;const e=u(n);if(x(e))return!0;const s=m(e);if(s===null)return!1;const i=e.includes(":")?128:32;return r.some(o=>o===e||o.includes("/")&&v(o,s,i))}let c=null;function A(){try{const n=$();if(!S(n))return[];const t=C(n).mtimeMs;if(c&&c.mtimeMs===t)return c.entries;const r=JSON.parse(w(n,"utf-8")),e=N(r?.accessControl?.allowIps);return c={mtimeMs:t,entries:e},e}catch(n){return p.warn(`[access-control] \u8BFB\u53D6 config.json \u5931\u8D25\uFF0C\u6CBF\u7528\u7F13\u5B58${c?"":"\uFF08\u65E0\u7F13\u5B58\uFF0C\u9ED8\u8BA4\u5168\u653E\u884C\uFF09"}: ${n.message}`),c?.entries??[]}}function F(){c=null}function M(n,t,r){const e=A(),s=h(n);if(!B(s,e)){p.warn(`[access-control] \u62D2\u7EDD\u6765\u81EA ${u(s)||"unknown"} \u7684\u8BF7\u6C42: ${n.method} ${n.originalUrl||n.url}`),t.status(403).json({ok:!1,error:"access denied: client IP is not allowed"});return}r()}function j(n,t){const r=A(),e=h(n);if(B(e,r))return!0;p.warn(`[access-control] \u62D2\u7EDD\u6765\u81EA ${u(e)||"unknown"} \u7684 WebSocket \u8FDE\u63A5: ${n.url}`);try{t.write(`HTTP/1.1 403 Forbidden\r
2
+ Connection: close\r
3
+ \r
4
+ `)}catch{}return t.destroy(),!1}export{M as accessControlMiddleware,A as getAccessControlEntries,B as isIpAllowed,j as isUpgradeAllowed,E as isValidIpEntry,N as parseAllowIps,F as refreshAccessControlCache};
5
+
@@ -1,2 +1,2 @@
1
- import{execSync as I}from"node:child_process";import k from"node:os";import{createLogger as S}from"../utils/logger.js";import{getProcessRegistry as R}from"./process-registry.js";import{isProcessRunning as l,findClaudeCodeProcesses as y,findOrphanProcessByAgentId as $,terminateProcessTree as g,waitForProcessExit as m,checkProcessHealth as v}from"./process-detector.js";import{loadPersistedSessions as w}from"./terminal-persistence.js";const r=S("agent-process-manager");class p{constructor(){this.registry=R()}static getInstance(){return p.instance===null&&(p.instance=new p),p.instance}async startProcess(e){const{agentId:s,sessionId:t,pid:a,mode:o,workspacePath:n,command:i}=e;if(r.info(`[startProcess] Starting process: agentId=${s}, pid=${a}, mode=${o}`),await this.registry.register({agentId:s,sessionId:t,pid:a,mode:o,state:"creating",workspacePath:n,command:i}),r.debug(`[startProcess] Verifying process is running: pid=${a}`),!l(a))throw r.error(`[startProcess] Process not running after registration: agentId=${s}, pid=${a}`),await this.registry.markTerminated(s),new Error(`Process ${a} is not running`);await this.registry.update(s,{state:"running"}),await this.registry.updateHeartbeat(s),r.info(`[startProcess] Process started successfully: agentId=${s}, pid=${a}`)}async discoverAllProcesses(){r.debug("[discoverAllProcesses] Scanning for all agent processes");const e=await y(),s=this.registry.getAll(),t=[],a=new Set(s.map(i=>i.pid)),o=new Set(s.map(i=>i.agentId)),n=new Map(s.map(i=>[i.pid,i]));for(const i of e){const d=a.has(i.pid),c=i.agentId&&o.has(i.agentId),h=n.get(i.pid),u=i.agentId||h?.agentId;if(d||c){const P=s.find(f=>f.pid===i.pid||f.agentId===i.agentId||f.agentId===u);t.push({...i,agentId:u,managementStatus:"managed",sessionId:P?.sessionId})}else u?t.push({...i,agentId:u,managementStatus:"orphan"}):t.push({...i,agentId:u,managementStatus:"unknown"})}for(const i of s)if(!t.find(c=>c.pid===i.pid)){const c=l(i.pid);t.push({pid:i.pid,command:i.command,agentId:i.agentId,managementStatus:c?"managed":"orphan",sessionId:i.sessionId})}return r.debug(`[discoverAllProcesses] Discovered ${t.length} processes: ${t.filter(i=>i.managementStatus==="managed").length} managed, ${t.filter(i=>i.managementStatus==="orphan").length} orphans, ${t.filter(i=>i.managementStatus==="unknown").length} unknown`),t}async healthCheckAll(){r.debug("[healthCheckAll] Starting health check for all processes");const e=new Map,s=this.registry.getAll(),t=s.map(async a=>{const o=await this.healthCheck(a.agentId);return e.set(a.agentId,o),o});return await Promise.all(t),r.debug(`[healthCheckAll] Completed health check for ${s.length} processes`),e}async healthCheck(e){const s=this.registry.get(e);if(!s)return r.warn(`[healthCheck] Process not found in registry: agentId=${e}`),{pid:-1,agentId:e,alive:!1,cpu:0,memoryMB:0,status:"dead",lastUpdate:new Date};const t=await v(s.pid,e),a={cpu:t.cpu,memoryMB:t.memoryMB,status:t.status};return await this.registry.updateHealth(e,a),t.alive?await this.registry.updateHeartbeat(e):s.state!=="terminated"&&s.state!=="orphaned"&&(r.warn(`[healthCheck] Process died unexpectedly: agentId=${e}, pid=${s.pid}`),await this.registry.markOrphaned(e)),t}async stopProcess(e){const s=this.registry.get(e);if(!s)return{success:!1,agentId:e,pid:-1,level:0,terminated:0,failed:0,message:`Process not found in registry: ${e}`};const{pid:t}=s;if(r.info(`[stopProcess] Stopping process: agentId=${e}, pid=${t}`),!l(t))return r.info(`[stopProcess] Process already stopped: agentId=${e}, pid=${t}`),await this.registry.markTerminated(e),{success:!0,agentId:e,pid:t,level:0,terminated:1,failed:0,message:"Process was already stopped"};if(await this.registry.update(e,{state:"stopping"}),r.debug(`[stopProcess] Level 1: Sending SIGTERM to pid=${t}`),this.sendSigterm(t)&&await m(t,3e3))return await this.registry.markTerminated(e),r.info(`[stopProcess] Process stopped at Level 1: agentId=${e}, pid=${t}`),{success:!0,agentId:e,pid:t,level:1,terminated:1,failed:0,message:"Process terminated with SIGTERM"};if(r.debug(`[stopProcess] Level 2: Force killing pid=${t}`),this.sendForceKill(t)&&await m(t,3e3))return await this.registry.markTerminated(e),r.info(`[stopProcess] Process stopped at Level 2: agentId=${e}, pid=${t}`),{success:!0,agentId:e,pid:t,level:2,terminated:1,failed:0,message:"Process terminated with force kill"};r.debug(`[stopProcess] Level 3: Terminating process tree for pid=${t}`);const n=await g(t);return await m(t,5e3)?(await this.registry.markTerminated(e),r.info(`[stopProcess] Process stopped at Level 3: agentId=${e}, pid=${t}, terminated=${n.terminated}, failed=${n.failed}`),{success:!0,agentId:e,pid:t,level:3,terminated:n.terminated,failed:n.failed,message:`Process tree terminated (${n.terminated} processes)`}):(r.error(`[stopProcess] Level 4: Process is unstoppable: agentId=${e}, pid=${t}`),(l(t)?"still running":"terminated")==="terminated"?(await this.registry.markTerminated(e),{success:!0,agentId:e,pid:t,level:4,terminated:1,failed:0,message:"Process eventually terminated after all escalation levels"}):{success:!1,agentId:e,pid:t,level:4,terminated:n.terminated,failed:n.failed+1,message:"Process could not be terminated - marked as unstoppable"})}sendSigterm(e){try{return process.kill(e,"SIGTERM"),!0}catch(s){return r.debug(`[sendSigterm] Failed to send SIGTERM to ${e}:`,s),!1}}sendForceKill(e){try{return k.platform()==="win32"?I(`taskkill /PID ${e} /F`,{encoding:"utf8",timeout:5e3,windowsHide:!0}):process.kill(e,"SIGKILL"),!0}catch(s){return r.debug(`[sendForceKill] Failed to force kill ${e}:`,s),!1}}async forceKill(e){const s=this.registry.get(e);if(!s)return{success:!1,agentId:e,pid:-1,level:0,terminated:0,failed:0,message:`Process not found in registry: ${e}`};const{pid:t}=s;r.info(`[forceKill] Force killing process: agentId=${e}, pid=${t}`),await this.registry.update(e,{state:"stopping"});const a=await g(t);return await m(t,2e3)?(await this.registry.markTerminated(e),r.info(`[forceKill] Process terminated: agentId=${e}, pid=${t}`),{success:!0,agentId:e,pid:t,level:3,terminated:a.terminated,failed:a.failed,message:`Process force killed (${a.terminated} processes terminated)`}):(r.error(`[forceKill] Process could not be force killed: agentId=${e}, pid=${t}`),{success:!1,agentId:e,pid:t,level:3,terminated:a.terminated,failed:a.failed+1,message:"Force kill failed - process may require manual intervention"})}async reclaimOrphan(e){if(r.info(`[reclaimOrphan] Attempting to reclaim orphan: agentId=${e}`),this.registry.isManaged(e)){const o=this.registry.get(e);return o?(r.info(`[reclaimOrphan] Process already managed: agentId=${e}, pid=${o.pid}`),{success:!0,pid:o.pid,action:"adopted"}):{success:!1,pid:-1,action:"failed"}}const s=await $(e);if(!s)return r.warn(`[reclaimOrphan] No orphan process found: agentId=${e}`),{success:!1,pid:-1,action:"failed"};if(!l(s.pid))return r.warn(`[reclaimOrphan] Orphan process not running: agentId=${e}, pid=${s.pid}`),{success:!1,pid:s.pid,action:"failed"};if(this.registry.isManagedByPid(s.pid)){const o=this.registry.getByPid(s.pid);if(!o)try{const i=(await w()).find(d=>d.agentId===e);return await this.registry.register({agentId:e,sessionId:i?.sessionId||`reclaimed-${Date.now()}`,pid:s.pid,mode:"sdk",state:"running",workspacePath:i?.workspacePath||s.cwd||"",command:s.command||i?.command||"",metadata:{reclaimedAt:new Date().toISOString(),wasOrphan:!0}}),{success:!0,pid:s.pid,action:"adopted"}}catch{return await g(s.pid),{success:!1,pid:s.pid,action:"terminated"}}return r.warn(`[reclaimOrphan] PID already managed by different agent: pid=${s.pid}, existingAgentId=${o.agentId}`),o.agentId===e?{success:!0,pid:s.pid,action:"adopted"}:(r.warn(`[reclaimOrphan] Terminating conflicting orphan: agentId=${e}, pid=${s.pid}`),await g(s.pid),{success:!0,pid:s.pid,action:"terminated"})}const a=(await w()).find(o=>o.agentId===e);try{return await this.registry.register({agentId:e,sessionId:a?.sessionId||`reclaimed-${Date.now()}`,pid:s.pid,mode:"sdk",state:"running",workspacePath:a?.workspacePath||s.cwd||"",command:s.command||a?.command||"",metadata:{reclaimedAt:new Date().toISOString(),wasOrphan:!0}}),r.info(`[reclaimOrphan] Successfully adopted orphan: agentId=${e}, pid=${s.pid}`),{success:!0,pid:s.pid,action:"adopted"}}catch(o){return r.error(`[reclaimOrphan] Failed to adopt orphan: agentId=${e}, pid=${s.pid}`,o),await g(s.pid),{success:!1,pid:s.pid,action:"terminated"}}}async scanAndReclaimAll(){r.info("[scanAndReclaimAll] Scanning for all orphan processes");const e=[],s=await w(),t=await y(),a=new Set(this.registry.getAll().map(n=>n.pid)),o=t.filter(n=>a.has(n.pid)?!1:!!(n.agentId&&!this.registry.isManaged(n.agentId)));r.info(`[scanAndReclaimAll] Found ${o.length} orphan processes`);for(const n of o){if(!n.agentId){r.warn(`[scanAndReclaimAll] Orphan without agentId, terminating: pid=${n.pid}`),await g(n.pid),e.push({agentId:"unknown",pid:n.pid,action:"terminated"});continue}const i=await this.reclaimOrphan(n.agentId);e.push({agentId:n.agentId,pid:i.pid,action:i.action})}for(const n of s)if(n.agentId&&!this.registry.isManaged(n.agentId)&&await $(n.agentId)){const d=await this.reclaimOrphan(n.agentId);e.find(h=>h.agentId===n.agentId)||e.push({agentId:n.agentId,pid:d.pid,action:d.action})}return r.info(`[scanAndReclaimAll] Reclamation complete: ${e.length} processes processed`),e}async markOrphaned(e){return r.info(`[markOrphaned] Marking process as orphaned: agentId=${e}`),await this.registry.markOrphaned(e)}isManaged(e){return this.registry.isManaged(e)}getAllManagedProcesses(){return this.registry.getAll()}getManagedProcessesByState(e){return this.registry.getByState(e)}async getUnmanagedProcesses(){const e=await y(),s=new Set(this.registry.getAll().map(a=>a.pid)),t=new Set(this.registry.getAll().map(a=>a.agentId));return e.filter(a=>!(s.has(a.pid)||a.agentId&&t.has(a.agentId)))}async rebuildRegistry(e){r.info(`[rebuildRegistry] Rebuilding registry from ${e.length} persisted sessions`),await this.registry.clear();for(const t of e){if(!t.pid){r.debug(`[rebuildRegistry] Skipping session without PID: agentId=${t.agentId}`);continue}const a=l(t.pid),o=a?"running":"unknown";try{await this.registry.register({agentId:t.agentId,sessionId:t.sessionId,pid:t.pid,mode:"sdk",state:o,workspacePath:t.workspacePath||"",command:t.command||"",metadata:{rebuiltAt:new Date().toISOString(),wasRunningOnStartup:a}}),r.debug(`[rebuildRegistry] Registered: agentId=${t.agentId}, pid=${t.pid}, state=${o}`)}catch(n){r.warn(`[rebuildRegistry] Failed to register: agentId=${t.agentId}`,n)}}const s=this.registry.getStats();r.info(`[rebuildRegistry] Registry rebuilt: total=${s.total}, running=${s.running}, unknown=${s.unknown}`)}getStats(){return this.registry.getStats()}async cleanupStaleRegistry(){const e=this.registry.getAll();let s=0;for(const t of e)(t.state==="terminated"||t.state==="unknown")&&!l(t.pid)&&(await this.registry.remove(t.agentId),s++,r.debug(`[cleanupStaleRegistry] Removed stale entry: agentId=${t.agentId}, pid=${t.pid}, state=${t.state}`));return s>0&&r.info(`[cleanupStaleRegistry] Cleaned up ${s} stale registry entries`),s}async removeProcess(e){return r.info(`[removeProcess] Removing process from registry: agentId=${e}`),await this.registry.remove(e)}getProcess(e){return this.registry.get(e)}getProcessByPid(e){return this.registry.getByPid(e)}}p.instance=null;function T(){return p.getInstance()}export{p as AgentProcessManager,T as getAgentProcessManager};
1
+ import{execSync as R}from"node:child_process";import v from"node:os";import{createLogger as A}from"../utils/logger.js";import{getProcessRegistry as b}from"./process-registry.js";import{isProcessRunning as p,isProcessRunningAsync as O,findClaudeCodeProcesses as w,findOrphanProcessByAgentId as I,terminateProcessTree as u,waitForProcessExit as f,checkProcessHealth as M}from"./process-detector.js";import{loadPersistedSessions as $}from"./terminal-persistence.js";const r=A("agent-process-manager");async function x(h,e,s){const t=new Array(h.length);let i=0;const o=Math.min(Math.max(e,1),h.length),a=Array.from({length:o},async()=>{for(;i<h.length;){const d=i++;t[d]=await s(h[d])}});return await Promise.all(a),t}class g{constructor(){this.registry=b()}static getInstance(){return g.instance===null&&(g.instance=new g),g.instance}async startProcess(e){const{agentId:s,sessionId:t,pid:i,mode:o,workspacePath:a,command:d}=e;if(r.info(`[startProcess] Starting process: agentId=${s}, pid=${i}, mode=${o}`),await this.registry.register({agentId:s,sessionId:t,pid:i,mode:o,state:"creating",workspacePath:a,command:d}),r.debug(`[startProcess] Verifying process is running: pid=${i}`),!p(i))throw r.error(`[startProcess] Process not running after registration: agentId=${s}, pid=${i}`),await this.registry.markTerminated(s),new Error(`Process ${i} is not running`);await this.registry.update(s,{state:"running"}),await this.registry.updateHeartbeat(s),r.info(`[startProcess] Process started successfully: agentId=${s}, pid=${i}`)}async discoverAllProcesses(){r.debug("[discoverAllProcesses] Scanning for all agent processes");const e=await w(),s=this.registry.getAll(),t=[],i=new Set(s.map(n=>n.pid)),o=new Set(s.map(n=>n.agentId)),a=new Map(s.map(n=>[n.pid,n]));for(const n of e){const l=i.has(n.pid),P=n.agentId&&o.has(n.agentId),k=a.get(n.pid),m=n.agentId||k?.agentId;if(l||P){const S=s.find(y=>y.pid===n.pid||y.agentId===n.agentId||y.agentId===m);t.push({...n,agentId:m,managementStatus:"managed",sessionId:S?.sessionId})}else m?t.push({...n,agentId:m,managementStatus:"orphan"}):t.push({...n,agentId:m,managementStatus:"unknown"})}const d=s.filter(n=>!t.some(l=>l.pid===n.pid)),c=await x(d,4,async n=>{const l=await O(n.pid);return{pid:n.pid,command:n.command,agentId:n.agentId,managementStatus:l?"managed":"orphan",sessionId:n.sessionId}});return t.push(...c),r.debug(`[discoverAllProcesses] Discovered ${t.length} processes: ${t.filter(n=>n.managementStatus==="managed").length} managed, ${t.filter(n=>n.managementStatus==="orphan").length} orphans, ${t.filter(n=>n.managementStatus==="unknown").length} unknown`),t}async healthCheckAll(){r.debug("[healthCheckAll] Starting health check for all processes");const e=new Map,s=this.registry.getAll(),t=s.map(async i=>{const o=await this.healthCheck(i.agentId);return e.set(i.agentId,o),o});return await Promise.all(t),r.debug(`[healthCheckAll] Completed health check for ${s.length} processes`),e}async healthCheck(e){const s=this.registry.get(e);if(!s)return r.warn(`[healthCheck] Process not found in registry: agentId=${e}`),{pid:-1,agentId:e,alive:!1,cpu:0,memoryMB:0,status:"dead",lastUpdate:new Date};const t=await M(s.pid,e),i={cpu:t.cpu,memoryMB:t.memoryMB,status:t.status};return await this.registry.updateHealth(e,i),t.alive?await this.registry.updateHeartbeat(e):s.state!=="terminated"&&s.state!=="orphaned"&&(r.warn(`[healthCheck] Process died unexpectedly: agentId=${e}, pid=${s.pid}`),await this.registry.markOrphaned(e)),t}async stopProcess(e){const s=this.registry.get(e);if(!s)return{success:!1,agentId:e,pid:-1,level:0,terminated:0,failed:0,message:`Process not found in registry: ${e}`};const{pid:t}=s;if(r.info(`[stopProcess] Stopping process: agentId=${e}, pid=${t}`),!p(t))return r.info(`[stopProcess] Process already stopped: agentId=${e}, pid=${t}`),await this.registry.markTerminated(e),{success:!0,agentId:e,pid:t,level:0,terminated:1,failed:0,message:"Process was already stopped"};if(await this.registry.update(e,{state:"stopping"}),r.debug(`[stopProcess] Level 1: Sending SIGTERM to pid=${t}`),this.sendSigterm(t)&&await f(t,3e3))return await this.registry.markTerminated(e),r.info(`[stopProcess] Process stopped at Level 1: agentId=${e}, pid=${t}`),{success:!0,agentId:e,pid:t,level:1,terminated:1,failed:0,message:"Process terminated with SIGTERM"};if(r.debug(`[stopProcess] Level 2: Force killing pid=${t}`),this.sendForceKill(t)&&await f(t,3e3))return await this.registry.markTerminated(e),r.info(`[stopProcess] Process stopped at Level 2: agentId=${e}, pid=${t}`),{success:!0,agentId:e,pid:t,level:2,terminated:1,failed:0,message:"Process terminated with force kill"};r.debug(`[stopProcess] Level 3: Terminating process tree for pid=${t}`);const a=await u(t);return await f(t,5e3)?(await this.registry.markTerminated(e),r.info(`[stopProcess] Process stopped at Level 3: agentId=${e}, pid=${t}, terminated=${a.terminated}, failed=${a.failed}`),{success:!0,agentId:e,pid:t,level:3,terminated:a.terminated,failed:a.failed,message:`Process tree terminated (${a.terminated} processes)`}):(r.error(`[stopProcess] Level 4: Process is unstoppable: agentId=${e}, pid=${t}`),(p(t)?"still running":"terminated")==="terminated"?(await this.registry.markTerminated(e),{success:!0,agentId:e,pid:t,level:4,terminated:1,failed:0,message:"Process eventually terminated after all escalation levels"}):{success:!1,agentId:e,pid:t,level:4,terminated:a.terminated,failed:a.failed+1,message:"Process could not be terminated - marked as unstoppable"})}sendSigterm(e){try{return process.kill(e,"SIGTERM"),!0}catch(s){return r.debug(`[sendSigterm] Failed to send SIGTERM to ${e}:`,s),!1}}sendForceKill(e){try{return v.platform()==="win32"?R(`taskkill /PID ${e} /F`,{encoding:"utf8",timeout:5e3,windowsHide:!0}):process.kill(e,"SIGKILL"),!0}catch(s){return r.debug(`[sendForceKill] Failed to force kill ${e}:`,s),!1}}async forceKill(e){const s=this.registry.get(e);if(!s)return{success:!1,agentId:e,pid:-1,level:0,terminated:0,failed:0,message:`Process not found in registry: ${e}`};const{pid:t}=s;r.info(`[forceKill] Force killing process: agentId=${e}, pid=${t}`),await this.registry.update(e,{state:"stopping"});const i=await u(t);return await f(t,2e3)?(await this.registry.markTerminated(e),r.info(`[forceKill] Process terminated: agentId=${e}, pid=${t}`),{success:!0,agentId:e,pid:t,level:3,terminated:i.terminated,failed:i.failed,message:`Process force killed (${i.terminated} processes terminated)`}):(r.error(`[forceKill] Process could not be force killed: agentId=${e}, pid=${t}`),{success:!1,agentId:e,pid:t,level:3,terminated:i.terminated,failed:i.failed+1,message:"Force kill failed - process may require manual intervention"})}async reclaimOrphan(e){if(r.info(`[reclaimOrphan] Attempting to reclaim orphan: agentId=${e}`),this.registry.isManaged(e)){const o=this.registry.get(e);return o?(r.info(`[reclaimOrphan] Process already managed: agentId=${e}, pid=${o.pid}`),{success:!0,pid:o.pid,action:"adopted"}):{success:!1,pid:-1,action:"failed"}}const s=await I(e);if(!s)return r.warn(`[reclaimOrphan] No orphan process found: agentId=${e}`),{success:!1,pid:-1,action:"failed"};if(!p(s.pid))return r.warn(`[reclaimOrphan] Orphan process not running: agentId=${e}, pid=${s.pid}`),{success:!1,pid:s.pid,action:"failed"};if(this.registry.isManagedByPid(s.pid)){const o=this.registry.getByPid(s.pid);if(!o)try{const d=(await $()).find(c=>c.agentId===e);return await this.registry.register({agentId:e,sessionId:d?.sessionId||`reclaimed-${Date.now()}`,pid:s.pid,mode:"sdk",state:"running",workspacePath:d?.workspacePath||s.cwd||"",command:s.command||d?.command||"",metadata:{reclaimedAt:new Date().toISOString(),wasOrphan:!0}}),{success:!0,pid:s.pid,action:"adopted"}}catch{return await u(s.pid),{success:!1,pid:s.pid,action:"terminated"}}return r.warn(`[reclaimOrphan] PID already managed by different agent: pid=${s.pid}, existingAgentId=${o.agentId}`),o.agentId===e?{success:!0,pid:s.pid,action:"adopted"}:(r.warn(`[reclaimOrphan] Terminating conflicting orphan: agentId=${e}, pid=${s.pid}`),await u(s.pid),{success:!0,pid:s.pid,action:"terminated"})}const i=(await $()).find(o=>o.agentId===e);try{return await this.registry.register({agentId:e,sessionId:i?.sessionId||`reclaimed-${Date.now()}`,pid:s.pid,mode:"sdk",state:"running",workspacePath:i?.workspacePath||s.cwd||"",command:s.command||i?.command||"",metadata:{reclaimedAt:new Date().toISOString(),wasOrphan:!0}}),r.info(`[reclaimOrphan] Successfully adopted orphan: agentId=${e}, pid=${s.pid}`),{success:!0,pid:s.pid,action:"adopted"}}catch(o){return r.error(`[reclaimOrphan] Failed to adopt orphan: agentId=${e}, pid=${s.pid}`,o),await u(s.pid),{success:!1,pid:s.pid,action:"terminated"}}}async scanAndReclaimAll(){r.info("[scanAndReclaimAll] Scanning for all orphan processes");const e=[],s=await $(),t=await w(),i=new Set(this.registry.getAll().map(a=>a.pid)),o=t.filter(a=>i.has(a.pid)?!1:!!(a.agentId&&!this.registry.isManaged(a.agentId)));r.info(`[scanAndReclaimAll] Found ${o.length} orphan processes`);for(const a of o){if(!a.agentId){r.warn(`[scanAndReclaimAll] Orphan without agentId, terminating: pid=${a.pid}`),await u(a.pid),e.push({agentId:"unknown",pid:a.pid,action:"terminated"});continue}const d=await this.reclaimOrphan(a.agentId);e.push({agentId:a.agentId,pid:d.pid,action:d.action})}for(const a of s)if(a.agentId&&!this.registry.isManaged(a.agentId)&&await I(a.agentId)){const c=await this.reclaimOrphan(a.agentId);e.find(l=>l.agentId===a.agentId)||e.push({agentId:a.agentId,pid:c.pid,action:c.action})}return r.info(`[scanAndReclaimAll] Reclamation complete: ${e.length} processes processed`),e}async markOrphaned(e){return r.info(`[markOrphaned] Marking process as orphaned: agentId=${e}`),await this.registry.markOrphaned(e)}isManaged(e){return this.registry.isManaged(e)}getAllManagedProcesses(){return this.registry.getAll()}getManagedProcessesByState(e){return this.registry.getByState(e)}async getUnmanagedProcesses(){const e=await w(),s=new Set(this.registry.getAll().map(i=>i.pid)),t=new Set(this.registry.getAll().map(i=>i.agentId));return e.filter(i=>!(s.has(i.pid)||i.agentId&&t.has(i.agentId)))}async rebuildRegistry(e){r.info(`[rebuildRegistry] Rebuilding registry from ${e.length} persisted sessions`),await this.registry.clear();for(const t of e){if(!t.pid){r.debug(`[rebuildRegistry] Skipping session without PID: agentId=${t.agentId}`);continue}const i=p(t.pid),o=i?"running":"unknown";try{await this.registry.register({agentId:t.agentId,sessionId:t.sessionId,pid:t.pid,mode:"sdk",state:o,workspacePath:t.workspacePath||"",command:t.command||"",metadata:{rebuiltAt:new Date().toISOString(),wasRunningOnStartup:i}}),r.debug(`[rebuildRegistry] Registered: agentId=${t.agentId}, pid=${t.pid}, state=${o}`)}catch(a){r.warn(`[rebuildRegistry] Failed to register: agentId=${t.agentId}`,a)}}const s=this.registry.getStats();r.info(`[rebuildRegistry] Registry rebuilt: total=${s.total}, running=${s.running}, unknown=${s.unknown}`)}getStats(){return this.registry.getStats()}async cleanupStaleRegistry(){const e=this.registry.getAll();let s=0;for(const t of e)(t.state==="terminated"||t.state==="unknown")&&!p(t.pid)&&(await this.registry.remove(t.agentId),s++,r.debug(`[cleanupStaleRegistry] Removed stale entry: agentId=${t.agentId}, pid=${t.pid}, state=${t.state}`));return s>0&&r.info(`[cleanupStaleRegistry] Cleaned up ${s} stale registry entries`),s}async removeProcess(e){return r.info(`[removeProcess] Removing process from registry: agentId=${e}`),await this.registry.remove(e)}getProcess(e){return this.registry.get(e)}getProcessByPid(e){return this.registry.getByPid(e)}}g.instance=null;function L(){return g.getInstance()}export{g as AgentProcessManager,L as getAgentProcessManager};
2
2
 
@@ -1,2 +1,2 @@
1
- import{v4 as M}from"uuid";import R from"axios";import m from"node:os";import F from"node:path";import g from"node:fs";import{logger as i}from"../utils/logger.js";import{schedulerBaseUrl as u}from"../config.js";import{clearRuntimeBinding as z,clearScopedRuntimeAccessToken as J,getRuntimeAccessToken as h,getRuntimeBindingPublicState as w,getScopedRuntimeAccessToken as Y,loadRuntimeBinding as v,normalizeSchedulerBaseUrl as I,saveRuntimeBinding as K,saveScopedRuntimeAccessToken as q}from"./runtime-binding.js";const C={enabled:!1,serverUrl:u,preferSameLanIp:!0,maxRetries:3,retryInterval:2e3};let c={registered:!1,registrations:{}},b=[];function Re(){return[...b]}const H=3e4;function P(){const e=Number(process.env.AWS_REGISTER_BACKGROUND_RETRY_INTERVAL);return Number.isFinite(e)&&e>0?e:H}const A=new Map;function W(e){const r=e.serverUrl||u,t=I(r)||r;if(A.has(t))return;const n=setTimeout(()=>{A.delete(t),V(e,t)},P());n.unref?.(),A.set(t,n)}async function V(e,r){try{const t=S().find(n=>(I(n.serverUrl||u)||n.serverUrl||u)===r)||e;await $(t,{backgroundRetry:!0})}catch(t){i.warn(`[AutoRegister] \u540E\u53F0\u91CD\u8BD5\u6CE8\u518C\u5F02\u5E38: ${t.message}`),W(e)}}function Ae(){for(const e of A.values())clearTimeout(e);A.clear()}function d(e){return e==null?void 0:String(e).trim()||void 0}function L(e){return I(e)}function T(e){if(typeof e=="boolean")return e;if(typeof e=="string"){const r=e.trim().toLowerCase();if(["true","1","yes","y"].includes(r))return!0;if(["false","0","no","n"].includes(r))return!1}}function O(e){if(e==null||e==="")return;const r=Number(e);return Number.isFinite(r)?r:void 0}function N(e){const r=d(e.serverUrl)||d(e.schedulerBaseUrl),t=d(e.connectionKey)||d(e.password);return{enabled:T(e.enabled),serverUrl:r,tenantId:d(e.tenantId),userKey:d(e.userKey),connectionKey:t,instanceName:d(e.instanceName),instanceId:d(e.instanceId),projectId:d(e.projectId),roleName:d(e.roleName),workspacePath:d(e.workspacePath),registerAddr:d(e.registerAddr),registerIp:d(e.registerIp),virtualIp:d(e.virtualIp),tunnel:T(e.tunnel),preferSameLanIp:T(e.preferSameLanIp),maxRetries:O(e.maxRetries),retryInterval:O(e.retryInterval)}}function k(){const e=String(process.env.AWS_RUNTIME_HOME_DIR||"").trim()||m.homedir();return F.join(e,".aws-bridge","config.json")}function x(){const e=k();try{if(!g.existsSync(e))return i.debug(`[AutoRegister] \u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${e}`),null;const r=g.readFileSync(e,"utf-8"),t=JSON.parse(r);return i.info(`[AutoRegister] \u4ECE\u914D\u7F6E\u6587\u4EF6\u52A0\u8F7D\u914D\u7F6E: ${e}`),N(t)}catch(r){const t=r;return i.warn(`[AutoRegister] \u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6\u5931\u8D25: ${e}, error: ${t.message}`),null}}function X(){const e=k();try{if(!g.existsSync(e))return[];const r=g.readFileSync(e,"utf-8"),t=JSON.parse(r),n=N(t),o=Array.isArray(t.autoRegisterTargets)?t.autoRegisterTargets:[];return o.length===0?n.userKey?[n]:[]:o.filter(s=>!!(s&&typeof s=="object"&&!Array.isArray(s))).map(s=>{const a=N(s);return{...n,...a,connectionKey:a.connectionKey||n.connectionKey}})}catch(r){const t=r;return i.warn(`[AutoRegister] \u8BFB\u53D6\u81EA\u52A8\u6CE8\u518C\u76EE\u6807\u5931\u8D25: ${e}, error: ${t.message}`),[]}}function j(){const e={};return process.env.AWS_AUTO_REGISTER==="true"&&(e.enabled=!0),process.env.AWS_RUNTIME_SCHEDULER_BASE_URL&&(e.serverUrl=process.env.AWS_RUNTIME_SCHEDULER_BASE_URL),process.env.AWS_TENANT_ID&&(e.tenantId=process.env.AWS_TENANT_ID),process.env.AWS_USER_KEY&&(e.userKey=process.env.AWS_USER_KEY),process.env.AWS_BRIDGE_CONNECTION_KEY&&(e.connectionKey=process.env.AWS_BRIDGE_CONNECTION_KEY),process.env.AWS_INSTANCE_NAME&&(e.instanceName=process.env.AWS_INSTANCE_NAME),process.env.AWS_PROJECT_NAME&&(e.projectId=process.env.AWS_PROJECT_NAME),process.env.AWS_ROLE_NAME&&(e.roleName=process.env.AWS_ROLE_NAME),process.env.AWS_WORKSPACE_PATH&&(e.workspacePath=process.env.AWS_WORKSPACE_PATH),process.env.AWS_REGISTER_ADDR&&(e.registerAddr=process.env.AWS_REGISTER_ADDR),process.env.AWS_REGISTER_IP&&(e.registerIp=process.env.AWS_REGISTER_IP),process.env.AWS_VIRTUAL_IP&&(e.virtualIp=process.env.AWS_VIRTUAL_IP),process.env.AWS_PREFER_SAME_LAN_IP&&(e.preferSameLanIp=T(process.env.AWS_PREFER_SAME_LAN_IP)??!0),process.env.AWS_REGISTER_MAX_RETRIES&&(e.maxRetries=parseInt(process.env.AWS_REGISTER_MAX_RETRIES,10)),process.env.AWS_REGISTER_RETRY_INTERVAL&&(e.retryInterval=parseInt(process.env.AWS_REGISTER_RETRY_INTERVAL,10)),e}function _(){const e=x(),r=j(),t={...C,...e||{},...r},n=t.instanceName||m.hostname();let o=!1;return process.env.AWS_AUTO_REGISTER!==void 0?o=process.env.AWS_AUTO_REGISTER==="true":e&&(o=!0),{enabled:o,serverUrl:t.serverUrl||u,tenantId:t.tenantId||"",userKey:t.userKey||"",connectionKey:t.connectionKey,instanceName:n,instanceId:t.instanceId,projectId:t.projectId,roleName:t.roleName,workspacePath:t.workspacePath,registerAddr:t.registerAddr,registerIp:t.registerIp,virtualIp:t.virtualIp,tunnel:typeof t.tunnel=="boolean"?t.tunnel:void 0,preferSameLanIp:t.preferSameLanIp!==!1,maxRetries:t.maxRetries,retryInterval:t.retryInterval}}function S(){const e=X(),r=j(),n=(e.length>0?e:[x()||{}]).map(o=>{const s={...C,...o,...r};let a=!1;return process.env.AWS_AUTO_REGISTER!==void 0?a=process.env.AWS_AUTO_REGISTER==="true":a=e.length>0||!!s.userKey,{enabled:a,serverUrl:s.serverUrl||u,tenantId:s.tenantId||"",userKey:s.userKey||"",connectionKey:s.connectionKey,instanceName:s.instanceName||m.hostname(),instanceId:s.instanceId,projectId:s.projectId,roleName:s.roleName,workspacePath:s.workspacePath,registerAddr:s.registerAddr,registerIp:s.registerIp,virtualIp:s.virtualIp,tunnel:typeof s.tunnel=="boolean"?s.tunnel:void 0,preferSameLanIp:s.preferSameLanIp!==!1,maxRetries:s.maxRetries,retryInterval:s.retryInterval}});return b=n.map(o=>o.connectionKey).filter(o=>!!(o&&o.trim())),n}function Q(){const e=m.networkInterfaces(),r=[];for(const t of Object.keys(e)){const n=e[t];if(n)for(const o of n)o.family==="IPv4"&&!o.internal&&r.push({address:o.address,netmask:o.netmask,name:t})}return r}function B(e){const r=e.split(".").map(Number);return(r[0]<<24)+(r[1]<<16)+(r[2]<<8)+r[3]}function Z(e,r,t){const n=B(e),o=B(r),s=B(t);return(n&s)===(o&s)}function ee(e){try{const r=new URL(e);return r.hostname==="localhost"||r.hostname==="127.0.0.1"?"127.0.0.1":/^(\d{1,3}\.){3}\d{1,3}$/.test(r.hostname)?r.hostname:null}catch{return null}}function re(e){return e==="127.0.0.1"}function te(e){const r=Q();if(r.length===0)return i.warn("[AutoRegister] \u672A\u627E\u5230\u4EFB\u4F55\u975E\u5185\u90E8 IP \u5730\u5740\uFF0C\u4F7F\u7528 127.0.0.1"),"127.0.0.1";const t=ee(e);if(t){if(re(t))return"127.0.0.1";for(const n of r)if(Z(n.address,t,n.netmask))return i.info(`[AutoRegister] \u627E\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\u540C\u5B50\u7F51\u7684 IP: ${n.address} (\u7F51\u5361: ${n.name}, \u5B50\u7F51\u63A9\u7801: ${n.netmask})`),n.address;i.warn(`[AutoRegister] \u672A\u627E\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3 (${t}) \u540C\u5B50\u7F51\u7684\u672C\u673A IP\uFF0C\u4F7F\u7528 127.0.0.1`)}else i.warn("[AutoRegister] \u65E0\u6CD5\u89E3\u6790\u8C03\u5EA6\u4E2D\u5FC3 IP\uFF0C\u4F7F\u7528 127.0.0.1");return"127.0.0.1"}function ne(e=u){return te(e)}function D(e){const r=e||_();return!r.enabled||!r.userKey?!1:r.tunnel===!0?!0:r.tunnel===!1?!1:!r.registerAddr&&!r.registerIp&&!r.virtualIp}function E(e){const r=process.env.AWS_RUNTIME_BRIDGE_PORT||18081;if(D(e)){const s=e.serverUrl||u;try{const a=new URL(s);if(!e.instanceId)i.warn("[AutoRegister] \u96A7\u9053\u6A21\u5F0F\u4F46\u7F3A\u5C11 instanceId\uFF0C\u964D\u7EA7\u4E3A\u76F4\u8FDE\u6A21\u5F0F");else return`${a.protocol}//${a.host}/tunnel/${e.instanceId}`}catch{i.warn("[AutoRegister] serverUrl \u89E3\u6790\u5931\u8D25\uFF0C\u964D\u7EA7\u4E3A\u76F4\u8FDE\u6A21\u5F0F")}}const t=e.preferSameLanIp===!1?"127.0.0.1":ne(e.serverUrl||u),n=e.registerAddr||e.registerIp||e.virtualIp||t;return n?/^https?:\/\//i.test(n)?n:/:[0-9]+$/.test(n)?`http://${n}`:`http://${n}:${r}`:`http://127.0.0.1:${r}`}function y(){return S()[0]||_()}function Se(e){const r=String(e||"").trim();if(!r)return;const t=[];for(const o of S())o?.serverUrl&&t.push(o.serverUrl);const n=v();n.schedulerBaseUrl&&t.push(n.schedulerBaseUrl);for(const o of t){const s=Y(o);if(s&&s===r)return I(o)}}function se(e){return e?Object.entries(c.registrations).find(([,t])=>t===e)?.[0]:void 0}function U(e,r){const t=se(r);if(t)return t;const n=d(e.serverUrl);if(n&&(n!==u||process.env.AWS_RUNTIME_SCHEDULER_BASE_URL))return n;const o=v(),s=d(o.schedulerBaseUrl);return o.status==="paired"&&s?s:n||u}async function oe(e){const r=e.instanceName||m.hostname(),t=e.serverUrl||u,n={tenantId:e.tenantId,userKey:e.userKey,instanceId:e.instanceId,instanceName:r,hostname:m.hostname(),virtualIp:e.registerAddr||e.registerIp||e.virtualIp,runtimeBridgeBaseUrl:E(e),connectionKey:e.connectionKey,workspacePath:e.workspacePath,projectId:e.projectId,roleName:e.roleName};return i.info(`[AutoRegister] \u6B63\u5728\u6CE8\u518C\u5B9E\u4F8B: ${r}`),i.info(`[AutoRegister] \u8C03\u5EA6\u4E2D\u5FC3: ${t}`),i.info(`[AutoRegister] \u7528\u6237 Key: ${e.userKey?"****"+e.userKey.slice(-4):"(\u672A\u8BBE\u7F6E)"}`),i.info(`[AutoRegister] \u79DF\u6237 ID: ${e.tenantId||"(\u81EA\u52A8\u63A8\u5BFC)"}`),(await R.post(`${t}/api/instances/register`,n,{headers:{"Content-Type":"application/json"},timeout:1e4})).data}let G=Promise.resolve();function ie(e,r){const t=G.then(()=>{const n=k();if(!g.existsSync(n))return;const o=g.readFileSync(n,"utf-8"),s=JSON.parse(o),a=I(e);if(!a)return;let l=!1;const f=p=>{I(String(p.serverUrl||p.schedulerBaseUrl||""))===a&&r&&p.instanceId!==r&&(p.instanceId=r,l=!0)};if(Array.isArray(s.autoRegisterTargets))for(const p of s.autoRegisterTargets)p&&typeof p=="object"&&!Array.isArray(p)&&f(p);else f(s);l&&(g.writeFileSync(n,JSON.stringify(s,null,2),"utf-8"),i.info(`[AutoRegister] \u5DF2\u56DE\u5199 instanceId \u5230 config.json (serverUrl=${e})`))});return G=t.catch(()=>{}),t}async function ae(e){const r=U(y(),e),t=v(),n=h(r);if(!n)throw new Error("runtimeAccessToken is required to unregister bridge instance");return(await R.post(`${r}/api/instances/unregister`,{instanceId:e},{headers:{"Content-Type":"application/json","X-Runtime-Token":n},timeout:1e4})).data}async function ye(e){const r=e?[{..._(),...e}]:S();return r.length>1?(await Promise.all(r.map(n=>$(n)))).every(Boolean):$(r[0]||_())}async function $(e,r){if(!e.enabled)return i.info("[AutoRegister] \u81EA\u52A8\u6CE8\u518C\u672A\u542F\u7528"),i.info("[AutoRegister] \u8981\u542F\u7528\u81EA\u52A8\u6CE8\u518C\uFF0C\u8BF7\u521B\u5EFA\u914D\u7F6E\u6587\u4EF6 ~/.aws-bridge/config.json \u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF AWS_AUTO_REGISTER=true"),!1;if(!e.userKey)return i.error("[AutoRegister] \u7F3A\u5C11\u7528\u6237 Key\uFF08\u5728\u914D\u7F6E\u6587\u4EF6\u4E2D\u8BBE\u7F6E userKey \u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF AWS_USER_KEY\uFF09"),!1;const t=D(e);t&&!e.instanceId&&(e={...e,instanceId:`bridge-${M().replace(/-/g,"").slice(0,12)}`},i.info(`[AutoRegister] \u96A7\u9053\u6A21\u5F0F\uFF1A\u5DF2\u9884\u58F0\u660E instanceId=${e.instanceId}`));const n=r?.backgroundRetry?1:e.maxRetries??3,o=e.retryInterval??2e3;for(let s=1;s<=n;s++){try{c.lastAttempt=new Date;const a=await oe(e);if(a.success){if(c.registered=!0,c.instanceId=a.instanceId,a.instanceId&&(c.registrations[e.serverUrl||u]=a.instanceId),c.error=void 0,i.info("[AutoRegister] \u2713 \u6CE8\u518C\u6210\u529F\uFF01"),i.info(`[AutoRegister] \u5B9E\u4F8B ID: ${a.instanceId}`),i.info(`[AutoRegister] \u6D88\u606F: ${a.message}`),i.info(`[AutoRegister] \u66F4\u65B0: ${a.isUpdate?"\u662F":"\u5426"}`),a.runtimeAccessToken)try{K({accessToken:a.runtimeAccessToken,instanceId:a.instanceId,userId:a.userId,schedulerBaseUrl:a.serverUrl||e.serverUrl||u}),i.info("[AutoRegister] \u5DF2\u4FDD\u5B58\u8C03\u5EA6\u4E2D\u5FC3\u4E0B\u53D1\u7684 runtime access token"),a.instanceId&&ie(e.serverUrl||u,a.instanceId).catch(l=>{i.warn(`[AutoRegister] \u56DE\u5199 instanceId \u5230 config.json \u5931\u8D25: ${l.message}`)})}catch(l){const f=l;return i.error(`[AutoRegister] \u4FDD\u5B58 runtime access token \u5931\u8D25: ${f.message}`),c.error=f.message,!1}else i.warn("[AutoRegister] \u6CE8\u518C\u54CD\u5E94\u672A\u5305\u542B runtime access token\uFF0CMCP \u5C06\u65E0\u6CD5\u8BBF\u95EE\u8C03\u5EA6\u4E2D\u5FC3\uFF0C\u8BF7\u68C0\u67E5 server \u7684 bridge \u5B9E\u4F8B token \u9881\u53D1\u903B\u8F91");if(t&&a.instanceId&&a.runtimeAccessToken){const{startTunnelClient:l}=await import("./tunnel-client.js"),f=e.serverUrl||u;l(f,a.instanceId),i.info(`[AutoRegister] \u96A7\u9053\u5BA2\u6237\u7AEF\u5DF2\u542F\u52A8: server=${f}, instanceId=${a.instanceId}`)}return!0}else i.warn(`[AutoRegister] \u6CE8\u518C\u5931\u8D25: ${a.message}`),c.error=a.message}catch(a){const l=a,f=l.response?.data?JSON.stringify(l.response.data):l.message;i.error(`[AutoRegister] \u6CE8\u518C\u8BF7\u6C42\u5931\u8D25 (\u5C1D\u8BD5 ${s}/${n}): ${f}`),c.error=f}s<n&&(i.info(`[AutoRegister] ${o/1e3} \u79D2\u540E\u91CD\u8BD5...`),await new Promise(a=>setTimeout(a,o)))}return r?.backgroundRetry?i.warn(`[AutoRegister] \u540E\u53F0\u91CD\u8BD5\u6CE8\u518C\u4ECD\u672A\u6210\u529F (server=${e.serverUrl||u}): ${c.error||"\u672A\u77E5"}`):(i.error("[AutoRegister] \u2717 \u6CE8\u518C\u5931\u8D25\uFF0C\u5DF2\u8FBE\u6700\u5927\u91CD\u8BD5\u6B21\u6570"),i.error("[AutoRegister] ========================================"),i.error("[AutoRegister] \u81EA\u52A8\u6CE8\u518C\u5DF2\u542F\u7528\u4F46\u6CE8\u518C\u5931\u8D25\uFF0C\u5B9E\u4F8B\u6682\u65F6\u65E0\u6CD5\u6B63\u5E38\u5DE5\u4F5C"),i.error("[AutoRegister] \u8BF7\u68C0\u67E5\u4EE5\u4E0B\u914D\u7F6E\uFF1A"),i.error(`[AutoRegister] - \u7528\u6237 Key (userKey): ${e.userKey?"****"+e.userKey.slice(-4):"(\u672A\u8BBE\u7F6E)"}`),i.error(`[AutoRegister] - \u79DF\u6237 ID (tenantId): ${e.tenantId||"(\u53EF\u9009\uFF0C\u5C06\u81EA\u52A8\u63A8\u5BFC)"}`),i.error(`[AutoRegister] - \u5B9E\u4F8B\u540D (instanceName): ${e.instanceName||"(\u4F7F\u7528\u4E3B\u673A\u540D)"}`),i.error(`[AutoRegister] - \u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740: ${e.serverUrl||u}`),i.error(`[AutoRegister] - \u6700\u540E\u9519\u8BEF: ${c.error||"\u672A\u77E5"}`),i.error("[AutoRegister] ========================================"),i.error("[AutoRegister] \u914D\u7F6E\u6765\u6E90\uFF1A~/.aws-bridge/config.json \u6216\u73AF\u5883\u53D8\u91CF"),i.warn(`[AutoRegister] \u5C06\u5728\u540E\u53F0\u6301\u7EED\u91CD\u8BD5\u6CE8\u518C\uFF08\u6BCF ${Math.round(P()/1e3)} \u79D2\uFF09\uFF0C\u76F4\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\u5EFA\u7ACB\u8FDE\u63A5`)),W(e),!1}async function ve(){const e=y();if(!e.userKey)return{success:!1,error:"userKey is required for runtime token refresh"};const r=w(),t=U(e),n=E(e);try{const o=await R.post(`${t}/api/instances/runtime-tokens/refresh`,{tenantId:e.tenantId,userKey:e.userKey,instanceId:r.instanceId,instanceName:e.instanceName,runtimeBridgeBaseUrl:n},{headers:{"Content-Type":"application/json"},timeout:1e4});if(!o.data.success||!o.data.runtimeAccessToken)return{success:!1,error:o.data.message||"scheduler did not return runtimeAccessToken"};const a=h()!==o.data.runtimeAccessToken;return K({accessToken:o.data.runtimeAccessToken,instanceId:r.instanceId,userId:o.data.userId||e.userKey,schedulerBaseUrl:t}),{success:!0,runtimeAccessToken:o.data.runtimeAccessToken,updated:a}}catch(o){const s=o;return{success:!1,error:s.response?.data?JSON.stringify(s.response.data):s.message}}}async function Te(e){const r=L(e);if(!r)return{success:!1,error:"serverUrl is required for runtime token refresh"};const t=S().find(s=>L(s.serverUrl)===r)||y();if(!t.userKey)return{success:!1,error:"userKey is required for runtime token refresh"};const n=w(),o=E(t);try{const s=await R.post(`${r}/api/instances/runtime-tokens/refresh`,{tenantId:t.tenantId,userKey:t.userKey,instanceId:n.instanceId,instanceName:t.instanceName,runtimeBridgeBaseUrl:o},{headers:{"Content-Type":"application/json"},timeout:1e4});if(!s.data.success||!s.data.runtimeAccessToken)return{success:!1,error:s.data.message||"scheduler did not return runtimeAccessToken"};const l=h(r)!==s.data.runtimeAccessToken;return q({serverBaseUrl:r,accessToken:s.data.runtimeAccessToken}),{success:!0,runtimeAccessToken:s.data.runtimeAccessToken,updated:l}}catch(s){const a=s;return{success:!1,error:a.response?.data?JSON.stringify(a.response.data):a.message}}}async function _e(){if(!c.registered||!c.instanceId)return i.info("[AutoRegister] \u5B9E\u4F8B\u672A\u6CE8\u518C\uFF0C\u65E0\u9700\u6CE8\u9500"),!0;try{const e=await ae(c.instanceId);if(e.success){i.info(`[AutoRegister] \u2713 \u6CE8\u9500\u6210\u529F: ${c.instanceId}`);const r=U(y(),c.instanceId);return v().schedulerBaseUrl&&J(r),z(),c.registered=!1,c.instanceId=void 0,!0}else return i.warn(`[AutoRegister] \u6CE8\u9500\u5931\u8D25: ${e.message}`),!1}catch(e){const r=e;return i.error(`[AutoRegister] \u6CE8\u9500\u8BF7\u6C42\u5931\u8D25: ${r.message}`),!1}}function Ee(){return{...c}}function Ue(){return c.registered}function he(){return c.instanceId}async function Ne(){const e=y(),r=c.instanceId,t=U(e,r),n={};if(r&&(n.instanceId=r),e.enabled&&(n.runtimeBridgeBaseUrl=E(e)),!n.instanceId&&!n.runtimeBridgeBaseUrl)return i.info("[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\uFF1A\u6CA1\u6709 instanceId \u6216 runtimeBridgeBaseUrl\uFF0C\u8DF3\u8FC7\u6E05\u7406"),{success:!0,agentCount:0};try{i.info("[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\uFF1A\u6B63\u5728\u901A\u77E5\u8C03\u5EA6\u4E2D\u5FC3..."),i.info(`[AutoRegister] instanceId: ${n.instanceId||"(\u65E0)"}`),i.info(`[AutoRegister] runtimeBridgeBaseUrl: ${n.runtimeBridgeBaseUrl||"(\u65E0)"}`);const o=await R.post(`${t}/api/instances/bridge-restart-cleanup`,n,{headers:{"Content-Type":"application/json"},timeout:1e4});return o.data.success?(i.info(`[AutoRegister] \u2713 Bridge \u91CD\u542F\u6E05\u7406\u6210\u529F\uFF1A${o.data.message}`),i.info(`[AutoRegister] \u6E05\u7406\u4E86 ${o.data.agentCount||0} \u4E2A Agent`),{success:!0,agentCount:o.data.agentCount||0}):(i.warn(`[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\u5931\u8D25\uFF1A${o.data.message}`),{success:!1,error:o.data.message})}catch(o){const s=o,a=s.response?.data?JSON.stringify(s.response.data):s.message;return i.warn(`[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\u8BF7\u6C42\u5931\u8D25\uFF1A${a}`),{success:!1,error:a}}}export{ye as autoRegister,Ne as bridgeRestartCleanup,Ae as cancelBackgroundRegisterRetries,k as getAutoRegisterConfigFilePath,Re as getConfiguredConnectionKeys,he as getInstanceId,Ee as getRegistrationState,Ue as isRegistered,D as isTunnelConfig,_ as loadConfig,S as loadConfigs,y as loadPrimaryLifecycleConfig,ve as requestRuntimeAccessTokenRefresh,Te as requestRuntimeAccessTokenRefreshForServer,Se as resolveSchedulerBaseUrlFromToken,_e as unregister};
1
+ import{v4 as G}from"uuid";import I from"axios";import m from"node:os";import M from"node:path";import g from"node:fs";import{logger as i}from"../utils/logger.js";import{schedulerBaseUrl as d}from"../config.js";import{runExclusiveConfigWrite as z}from"../utils/config-write-queue.js";import{clearRuntimeBinding as J,clearScopedRuntimeAccessToken as Y,getRuntimeAccessToken as E,getRuntimeBindingPublicState as w,getScopedRuntimeAccessToken as q,loadRuntimeBinding as v,normalizeSchedulerBaseUrl as p,saveRuntimeBinding as K,saveScopedRuntimeAccessToken as C}from"./runtime-binding.js";const b={enabled:!1,serverUrl:d,preferSameLanIp:!0,maxRetries:3,retryInterval:2e3};let u={registered:!1,registrations:{}},P=[];function Ae(){return[...P]}const H=3e4;function W(){const e=Number(process.env.AWS_REGISTER_BACKGROUND_RETRY_INTERVAL);return Number.isFinite(e)&&e>0?e:H}const A=new Map;function L(e){const r=e.serverUrl||d,t=p(r)||r;if(A.has(t))return;const n=setTimeout(()=>{A.delete(t),V(e,t)},W());n.unref?.(),A.set(t,n)}async function V(e,r){try{const t=R().find(n=>(p(n.serverUrl||d)||n.serverUrl||d)===r)||e;await B(t,{backgroundRetry:!0})}catch(t){i.warn(`[AutoRegister] \u540E\u53F0\u91CD\u8BD5\u6CE8\u518C\u5F02\u5E38: ${t.message}`),L(e)}}function Re(){for(const e of A.values())clearTimeout(e);A.clear()}function f(e){return e==null?void 0:String(e).trim()||void 0}function O(e){return p(e)}function y(e){if(typeof e=="boolean")return e;if(typeof e=="string"){const r=e.trim().toLowerCase();if(["true","1","yes","y"].includes(r))return!0;if(["false","0","no","n"].includes(r))return!1}}function x(e){if(e==null||e==="")return;const r=Number(e);return Number.isFinite(r)?r:void 0}function h(e){const r=f(e.serverUrl)||f(e.schedulerBaseUrl),t=f(e.connectionKey)||f(e.password);return{enabled:y(e.enabled),serverUrl:r,tenantId:f(e.tenantId),userKey:f(e.userKey),connectionKey:t,instanceName:f(e.instanceName),instanceId:f(e.instanceId),projectId:f(e.projectId),roleName:f(e.roleName),workspacePath:f(e.workspacePath),registerAddr:f(e.registerAddr),registerIp:f(e.registerIp),virtualIp:f(e.virtualIp),tunnel:y(e.tunnel),preferSameLanIp:y(e.preferSameLanIp),maxRetries:x(e.maxRetries),retryInterval:x(e.retryInterval)}}function N(){const e=String(process.env.AWS_RUNTIME_HOME_DIR||"").trim()||m.homedir();return M.join(e,".aws-bridge","config.json")}function j(){const e=N();try{if(!g.existsSync(e))return i.debug(`[AutoRegister] \u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${e}`),null;const r=g.readFileSync(e,"utf-8"),t=JSON.parse(r);return i.info(`[AutoRegister] \u4ECE\u914D\u7F6E\u6587\u4EF6\u52A0\u8F7D\u914D\u7F6E: ${e}`),h(t)}catch(r){const t=r;return i.warn(`[AutoRegister] \u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6\u5931\u8D25: ${e}, error: ${t.message}`),null}}function X(){const e=N();try{if(!g.existsSync(e))return[];const r=g.readFileSync(e,"utf-8"),t=JSON.parse(r),n=h(t),o=Array.isArray(t.autoRegisterTargets)?t.autoRegisterTargets:[];return o.length===0?n.userKey?[n]:[]:o.filter(s=>!!(s&&typeof s=="object"&&!Array.isArray(s))).map(s=>{const a=h(s);return{...n,...a,connectionKey:a.connectionKey||n.connectionKey}})}catch(r){const t=r;return i.warn(`[AutoRegister] \u8BFB\u53D6\u81EA\u52A8\u6CE8\u518C\u76EE\u6807\u5931\u8D25: ${e}, error: ${t.message}`),[]}}function D(){const e={};return process.env.AWS_AUTO_REGISTER==="true"&&(e.enabled=!0),process.env.AWS_RUNTIME_SCHEDULER_BASE_URL&&(e.serverUrl=process.env.AWS_RUNTIME_SCHEDULER_BASE_URL),process.env.AWS_TENANT_ID&&(e.tenantId=process.env.AWS_TENANT_ID),process.env.AWS_USER_KEY&&(e.userKey=process.env.AWS_USER_KEY),process.env.AWS_BRIDGE_CONNECTION_KEY&&(e.connectionKey=process.env.AWS_BRIDGE_CONNECTION_KEY),process.env.AWS_INSTANCE_NAME&&(e.instanceName=process.env.AWS_INSTANCE_NAME),process.env.AWS_PROJECT_NAME&&(e.projectId=process.env.AWS_PROJECT_NAME),process.env.AWS_ROLE_NAME&&(e.roleName=process.env.AWS_ROLE_NAME),process.env.AWS_WORKSPACE_PATH&&(e.workspacePath=process.env.AWS_WORKSPACE_PATH),process.env.AWS_REGISTER_ADDR&&(e.registerAddr=process.env.AWS_REGISTER_ADDR),process.env.AWS_REGISTER_IP&&(e.registerIp=process.env.AWS_REGISTER_IP),process.env.AWS_VIRTUAL_IP&&(e.virtualIp=process.env.AWS_VIRTUAL_IP),process.env.AWS_PREFER_SAME_LAN_IP&&(e.preferSameLanIp=y(process.env.AWS_PREFER_SAME_LAN_IP)??!0),process.env.AWS_REGISTER_MAX_RETRIES&&(e.maxRetries=parseInt(process.env.AWS_REGISTER_MAX_RETRIES,10)),process.env.AWS_REGISTER_RETRY_INTERVAL&&(e.retryInterval=parseInt(process.env.AWS_REGISTER_RETRY_INTERVAL,10)),e}function T(){const e=j(),r=D(),t={...b,...e||{},...r},n=t.instanceName||m.hostname();let o=!1;return process.env.AWS_AUTO_REGISTER!==void 0?o=process.env.AWS_AUTO_REGISTER==="true":e&&(o=!0),{enabled:o,serverUrl:t.serverUrl||d,tenantId:t.tenantId||"",userKey:t.userKey||"",connectionKey:t.connectionKey,instanceName:n,instanceId:t.instanceId,projectId:t.projectId,roleName:t.roleName,workspacePath:t.workspacePath,registerAddr:t.registerAddr,registerIp:t.registerIp,virtualIp:t.virtualIp,tunnel:typeof t.tunnel=="boolean"?t.tunnel:void 0,preferSameLanIp:t.preferSameLanIp!==!1,maxRetries:t.maxRetries,retryInterval:t.retryInterval}}function R(){const e=X(),r=D(),n=(e.length>0?e:[j()||{}]).map(o=>{const s={...b,...o,...r};let a=!1;return process.env.AWS_AUTO_REGISTER!==void 0?a=process.env.AWS_AUTO_REGISTER==="true":a=e.length>0||!!s.userKey,{enabled:a,serverUrl:s.serverUrl||d,tenantId:s.tenantId||"",userKey:s.userKey||"",connectionKey:s.connectionKey,instanceName:s.instanceName||m.hostname(),instanceId:s.instanceId,projectId:s.projectId,roleName:s.roleName,workspacePath:s.workspacePath,registerAddr:s.registerAddr,registerIp:s.registerIp,virtualIp:s.virtualIp,tunnel:typeof s.tunnel=="boolean"?s.tunnel:void 0,preferSameLanIp:s.preferSameLanIp!==!1,maxRetries:s.maxRetries,retryInterval:s.retryInterval}});return P=n.map(o=>o.connectionKey).filter(o=>!!(o&&o.trim())),n}function Q(){const e=m.networkInterfaces(),r=[];for(const t of Object.keys(e)){const n=e[t];if(n)for(const o of n)o.family==="IPv4"&&!o.internal&&r.push({address:o.address,netmask:o.netmask,name:t})}return r}function k(e){const r=e.split(".").map(Number);return(r[0]<<24)+(r[1]<<16)+(r[2]<<8)+r[3]}function Z(e,r,t){const n=k(e),o=k(r),s=k(t);return(n&s)===(o&s)}function ee(e){try{const r=new URL(e);return r.hostname==="localhost"||r.hostname==="127.0.0.1"?"127.0.0.1":/^(\d{1,3}\.){3}\d{1,3}$/.test(r.hostname)?r.hostname:null}catch{return null}}function re(e){return e==="127.0.0.1"}function te(e){const r=Q();if(r.length===0)return i.warn("[AutoRegister] \u672A\u627E\u5230\u4EFB\u4F55\u975E\u5185\u90E8 IP \u5730\u5740\uFF0C\u4F7F\u7528 127.0.0.1"),"127.0.0.1";const t=ee(e);if(t){if(re(t))return"127.0.0.1";for(const n of r)if(Z(n.address,t,n.netmask))return i.info(`[AutoRegister] \u627E\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\u540C\u5B50\u7F51\u7684 IP: ${n.address} (\u7F51\u5361: ${n.name}, \u5B50\u7F51\u63A9\u7801: ${n.netmask})`),n.address;i.warn(`[AutoRegister] \u672A\u627E\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3 (${t}) \u540C\u5B50\u7F51\u7684\u672C\u673A IP\uFF0C\u4F7F\u7528 127.0.0.1`)}else i.warn("[AutoRegister] \u65E0\u6CD5\u89E3\u6790\u8C03\u5EA6\u4E2D\u5FC3 IP\uFF0C\u4F7F\u7528 127.0.0.1");return"127.0.0.1"}function ne(e=d){return te(e)}function F(e){const r=e||T();return!r.enabled||!r.userKey?!1:r.tunnel===!0?!0:r.tunnel===!1?!1:!r.registerAddr&&!r.registerIp&&!r.virtualIp}function _(e){const r=process.env.AWS_RUNTIME_BRIDGE_PORT||18081;if(F(e)){const s=e.serverUrl||d;try{const a=new URL(s);if(!e.instanceId)i.warn("[AutoRegister] \u96A7\u9053\u6A21\u5F0F\u4F46\u7F3A\u5C11 instanceId\uFF0C\u964D\u7EA7\u4E3A\u76F4\u8FDE\u6A21\u5F0F");else return`${a.protocol}//${a.host}/tunnel/${e.instanceId}`}catch{i.warn("[AutoRegister] serverUrl \u89E3\u6790\u5931\u8D25\uFF0C\u964D\u7EA7\u4E3A\u76F4\u8FDE\u6A21\u5F0F")}}const t=e.preferSameLanIp===!1?"127.0.0.1":ne(e.serverUrl||d),n=e.registerAddr||e.registerIp||e.virtualIp||t;return n?/^https?:\/\//i.test(n)?n:/:[0-9]+$/.test(n)?`http://${n}`:`http://${n}:${r}`:`http://127.0.0.1:${r}`}function S(){return R()[0]||T()}function Se(e){const r=String(e||"").trim();if(!r)return;const t=[];for(const o of R())o?.serverUrl&&t.push(o.serverUrl);const n=v();n.schedulerBaseUrl&&t.push(n.schedulerBaseUrl);for(const o of t){const s=q(o);if(s&&s===r)return p(o)}}function se(e){return e?Object.entries(u.registrations).find(([,t])=>t===e)?.[0]:void 0}function U(e,r){const t=se(r);if(t)return t;const n=f(e.serverUrl);if(n&&(n!==d||process.env.AWS_RUNTIME_SCHEDULER_BASE_URL))return n;const o=v(),s=f(o.schedulerBaseUrl);return o.status==="paired"&&s?s:n||d}async function oe(e){const r=e.instanceName||m.hostname(),t=e.serverUrl||d,n={tenantId:e.tenantId,userKey:e.userKey,instanceId:e.instanceId,instanceName:r,hostname:m.hostname(),virtualIp:e.registerAddr||e.registerIp||e.virtualIp,runtimeBridgeBaseUrl:_(e),connectionKey:e.connectionKey,workspacePath:e.workspacePath,projectId:e.projectId,roleName:e.roleName};return i.info(`[AutoRegister] \u6B63\u5728\u6CE8\u518C\u5B9E\u4F8B: ${r}`),i.info(`[AutoRegister] \u8C03\u5EA6\u4E2D\u5FC3: ${t}`),i.info(`[AutoRegister] \u7528\u6237 Key: ${e.userKey?"****"+e.userKey.slice(-4):"(\u672A\u8BBE\u7F6E)"}`),i.info(`[AutoRegister] \u79DF\u6237 ID: ${e.tenantId||"(\u81EA\u52A8\u63A8\u5BFC)"}`),(await I.post(`${t}/api/instances/register`,n,{headers:{"Content-Type":"application/json"},timeout:1e4})).data}function ie(e,r){const t=N();return z(t,()=>{if(!g.existsSync(t))return;const n=g.readFileSync(t,"utf-8"),o=JSON.parse(n),s=p(e);if(!s)return;let a=!1;const l=c=>{p(String(c.serverUrl||c.schedulerBaseUrl||""))===s&&r&&c.instanceId!==r&&(c.instanceId=r,a=!0)};if(Array.isArray(o.autoRegisterTargets))for(const c of o.autoRegisterTargets)c&&typeof c=="object"&&!Array.isArray(c)&&l(c);else l(o);a&&(g.writeFileSync(t,JSON.stringify(o,null,2),"utf-8"),i.info(`[AutoRegister] \u5DF2\u56DE\u5199 instanceId \u5230 config.json (serverUrl=${e})`))})}async function ae(e){const r=U(S(),e),t=v(),n=E(r);if(!n)throw new Error("runtimeAccessToken is required to unregister bridge instance");return(await I.post(`${r}/api/instances/unregister`,{instanceId:e},{headers:{"Content-Type":"application/json","X-Runtime-Token":n},timeout:1e4})).data}async function ve(e){const r=e?[{...T(),...e}]:R(),n=(r.length>1?await Promise.all(r.map(s=>B(s))):[await B(r[0]||T())]).every(Boolean),o=r.every(s=>!s.enabled||!s.userKey);if(!e&&(n||o)){const{stopTunnelClientsExcept:s}=await import("./tunnel-client.js");s(r.map(a=>a.serverUrl).filter(a=>!!a))}return n}async function B(e,r){if(!e.enabled)return i.info("[AutoRegister] \u81EA\u52A8\u6CE8\u518C\u672A\u542F\u7528"),i.info("[AutoRegister] \u8981\u542F\u7528\u81EA\u52A8\u6CE8\u518C\uFF0C\u8BF7\u521B\u5EFA\u914D\u7F6E\u6587\u4EF6 ~/.aws-bridge/config.json \u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF AWS_AUTO_REGISTER=true"),!1;if(!e.userKey)return i.error("[AutoRegister] \u7F3A\u5C11\u7528\u6237 Key\uFF08\u5728\u914D\u7F6E\u6587\u4EF6\u4E2D\u8BBE\u7F6E userKey \u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF AWS_USER_KEY\uFF09"),!1;const t=F(e);t&&!e.instanceId&&(e={...e,instanceId:`bridge-${G().replace(/-/g,"").slice(0,12)}`},i.info(`[AutoRegister] \u96A7\u9053\u6A21\u5F0F\uFF1A\u5DF2\u9884\u58F0\u660E instanceId=${e.instanceId}`));const n=r?.backgroundRetry?1:e.maxRetries??3,o=e.retryInterval??2e3;for(let s=1;s<=n;s++){try{u.lastAttempt=new Date;const a=await oe(e);if(a.success){if(u.registered=!0,u.instanceId=a.instanceId,a.instanceId&&(u.registrations[e.serverUrl||d]=a.instanceId),u.error=void 0,i.info("[AutoRegister] \u2713 \u6CE8\u518C\u6210\u529F\uFF01"),i.info(`[AutoRegister] \u5B9E\u4F8B ID: ${a.instanceId}`),i.info(`[AutoRegister] \u6D88\u606F: ${a.message}`),i.info(`[AutoRegister] \u66F4\u65B0: ${a.isUpdate?"\u662F":"\u5426"}`),a.runtimeAccessToken)try{const l=e.serverUrl||d;K({accessToken:a.runtimeAccessToken,instanceId:a.instanceId,userId:a.userId,schedulerBaseUrl:l});const c=p(a.serverUrl);c&&c!==p(l)&&(C({serverBaseUrl:c,accessToken:a.runtimeAccessToken}),i.info(`[AutoRegister] \u68C0\u6D4B\u5230 server \u4E0B\u53D1\u5730\u5740(${c})\u4E0E\u8FDE\u63A5\u5730\u5740(${l})\u4E0D\u540C\uFF0C\u5DF2\u6309\u4E24\u4E2A\u5730\u5740\u5206\u522B\u4FDD\u5B58 scoped token`)),i.info("[AutoRegister] \u5DF2\u4FDD\u5B58\u8C03\u5EA6\u4E2D\u5FC3\u4E0B\u53D1\u7684 runtime access token"),a.instanceId&&ie(e.serverUrl||d,a.instanceId).catch($=>{i.warn(`[AutoRegister] \u56DE\u5199 instanceId \u5230 config.json \u5931\u8D25: ${$.message}`)})}catch(l){const c=l;return i.error(`[AutoRegister] \u4FDD\u5B58 runtime access token \u5931\u8D25: ${c.message}`),u.error=c.message,!1}else i.warn("[AutoRegister] \u6CE8\u518C\u54CD\u5E94\u672A\u5305\u542B runtime access token\uFF0CMCP \u5C06\u65E0\u6CD5\u8BBF\u95EE\u8C03\u5EA6\u4E2D\u5FC3\uFF0C\u8BF7\u68C0\u67E5 server \u7684 bridge \u5B9E\u4F8B token \u9881\u53D1\u903B\u8F91");if(t&&a.instanceId&&a.runtimeAccessToken){const{startTunnelClient:l}=await import("./tunnel-client.js"),c=e.serverUrl||d;l(c,a.instanceId),i.info(`[AutoRegister] \u96A7\u9053\u5BA2\u6237\u7AEF\u5DF2\u542F\u52A8: server=${c}, instanceId=${a.instanceId}`)}return!0}else i.warn(`[AutoRegister] \u6CE8\u518C\u5931\u8D25: ${a.message}`),u.error=a.message}catch(a){const l=a,c=l.response?.data?JSON.stringify(l.response.data):l.message;i.error(`[AutoRegister] \u6CE8\u518C\u8BF7\u6C42\u5931\u8D25 (\u5C1D\u8BD5 ${s}/${n}): ${c}`),u.error=c}s<n&&(i.info(`[AutoRegister] ${o/1e3} \u79D2\u540E\u91CD\u8BD5...`),await new Promise(a=>setTimeout(a,o)))}return r?.backgroundRetry?i.warn(`[AutoRegister] \u540E\u53F0\u91CD\u8BD5\u6CE8\u518C\u4ECD\u672A\u6210\u529F (server=${e.serverUrl||d}): ${u.error||"\u672A\u77E5"}`):(i.error("[AutoRegister] \u2717 \u6CE8\u518C\u5931\u8D25\uFF0C\u5DF2\u8FBE\u6700\u5927\u91CD\u8BD5\u6B21\u6570"),i.error("[AutoRegister] ========================================"),i.error("[AutoRegister] \u81EA\u52A8\u6CE8\u518C\u5DF2\u542F\u7528\u4F46\u6CE8\u518C\u5931\u8D25\uFF0C\u5B9E\u4F8B\u6682\u65F6\u65E0\u6CD5\u6B63\u5E38\u5DE5\u4F5C"),i.error("[AutoRegister] \u8BF7\u68C0\u67E5\u4EE5\u4E0B\u914D\u7F6E\uFF1A"),i.error(`[AutoRegister] - \u7528\u6237 Key (userKey): ${e.userKey?"****"+e.userKey.slice(-4):"(\u672A\u8BBE\u7F6E)"}`),i.error(`[AutoRegister] - \u79DF\u6237 ID (tenantId): ${e.tenantId||"(\u53EF\u9009\uFF0C\u5C06\u81EA\u52A8\u63A8\u5BFC)"}`),i.error(`[AutoRegister] - \u5B9E\u4F8B\u540D (instanceName): ${e.instanceName||"(\u4F7F\u7528\u4E3B\u673A\u540D)"}`),i.error(`[AutoRegister] - \u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740: ${e.serverUrl||d}`),i.error(`[AutoRegister] - \u6700\u540E\u9519\u8BEF: ${u.error||"\u672A\u77E5"}`),i.error("[AutoRegister] ========================================"),i.error("[AutoRegister] \u914D\u7F6E\u6765\u6E90\uFF1A~/.aws-bridge/config.json \u6216\u73AF\u5883\u53D8\u91CF"),i.warn(`[AutoRegister] \u5C06\u5728\u540E\u53F0\u6301\u7EED\u91CD\u8BD5\u6CE8\u518C\uFF08\u6BCF ${Math.round(W()/1e3)} \u79D2\uFF09\uFF0C\u76F4\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\u5EFA\u7ACB\u8FDE\u63A5`)),L(e),!1}async function ye(){const e=S();if(!e.userKey)return{success:!1,error:"userKey is required for runtime token refresh"};const r=w(),t=U(e),n=_(e);try{const o=await I.post(`${t}/api/instances/runtime-tokens/refresh`,{tenantId:e.tenantId,userKey:e.userKey,instanceId:r.instanceId,instanceName:e.instanceName,runtimeBridgeBaseUrl:n},{headers:{"Content-Type":"application/json"},timeout:1e4});if(!o.data.success||!o.data.runtimeAccessToken)return{success:!1,error:o.data.message||"scheduler did not return runtimeAccessToken"};const a=E()!==o.data.runtimeAccessToken;return K({accessToken:o.data.runtimeAccessToken,instanceId:r.instanceId,userId:o.data.userId||e.userKey,schedulerBaseUrl:t}),{success:!0,runtimeAccessToken:o.data.runtimeAccessToken,updated:a}}catch(o){const s=o;return{success:!1,error:s.response?.data?JSON.stringify(s.response.data):s.message}}}async function Te(e){const r=O(e);if(!r)return{success:!1,error:"serverUrl is required for runtime token refresh"};const t=R().find(s=>O(s.serverUrl)===r)||S();if(!t.userKey)return{success:!1,error:"userKey is required for runtime token refresh"};const n=w(),o=_(t);try{const s=await I.post(`${r}/api/instances/runtime-tokens/refresh`,{tenantId:t.tenantId,userKey:t.userKey,instanceId:n.instanceId,instanceName:t.instanceName,runtimeBridgeBaseUrl:o},{headers:{"Content-Type":"application/json"},timeout:1e4});if(!s.data.success||!s.data.runtimeAccessToken)return{success:!1,error:s.data.message||"scheduler did not return runtimeAccessToken"};const l=E(r)!==s.data.runtimeAccessToken;return C({serverBaseUrl:r,accessToken:s.data.runtimeAccessToken}),{success:!0,runtimeAccessToken:s.data.runtimeAccessToken,updated:l}}catch(s){const a=s;return{success:!1,error:a.response?.data?JSON.stringify(a.response.data):a.message}}}async function _e(){if(!u.registered||!u.instanceId)return i.info("[AutoRegister] \u5B9E\u4F8B\u672A\u6CE8\u518C\uFF0C\u65E0\u9700\u6CE8\u9500"),!0;try{const e=await ae(u.instanceId);if(e.success){i.info(`[AutoRegister] \u2713 \u6CE8\u9500\u6210\u529F: ${u.instanceId}`);const r=U(S(),u.instanceId);v().schedulerBaseUrl&&Y(r),J();try{const{stopTunnelClientForServer:n}=await import("./tunnel-client.js");n(r)}catch(n){i.warn(`[AutoRegister] \u6CE8\u9500\u540E\u505C\u6B62\u96A7\u9053\u5931\u8D25: ${n.message}`)}return u.registered=!1,u.instanceId=void 0,!0}else return i.warn(`[AutoRegister] \u6CE8\u9500\u5931\u8D25: ${e.message}`),!1}catch(e){const r=e;return i.error(`[AutoRegister] \u6CE8\u9500\u8BF7\u6C42\u5931\u8D25: ${r.message}`),!1}}function Ue(){return{...u}}function Ee(){return u.registered}function he(){return u.instanceId}async function Ne(){const e=S(),r=u.instanceId,t=U(e,r),n={};if(r&&(n.instanceId=r),e.enabled&&(n.runtimeBridgeBaseUrl=_(e)),!n.instanceId&&!n.runtimeBridgeBaseUrl)return i.info("[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\uFF1A\u6CA1\u6709 instanceId \u6216 runtimeBridgeBaseUrl\uFF0C\u8DF3\u8FC7\u6E05\u7406"),{success:!0,agentCount:0};try{i.info("[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\uFF1A\u6B63\u5728\u901A\u77E5\u8C03\u5EA6\u4E2D\u5FC3..."),i.info(`[AutoRegister] instanceId: ${n.instanceId||"(\u65E0)"}`),i.info(`[AutoRegister] runtimeBridgeBaseUrl: ${n.runtimeBridgeBaseUrl||"(\u65E0)"}`);const o=await I.post(`${t}/api/instances/bridge-restart-cleanup`,n,{headers:{"Content-Type":"application/json"},timeout:1e4});return o.data.success?(i.info(`[AutoRegister] \u2713 Bridge \u91CD\u542F\u6E05\u7406\u6210\u529F\uFF1A${o.data.message}`),i.info(`[AutoRegister] \u6E05\u7406\u4E86 ${o.data.agentCount||0} \u4E2A Agent`),{success:!0,agentCount:o.data.agentCount||0}):(i.warn(`[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\u5931\u8D25\uFF1A${o.data.message}`),{success:!1,error:o.data.message})}catch(o){const s=o,a=s.response?.data?JSON.stringify(s.response.data):s.message;return i.warn(`[AutoRegister] Bridge \u91CD\u542F\u6E05\u7406\u8BF7\u6C42\u5931\u8D25\uFF1A${a}`),{success:!1,error:a}}}export{ve as autoRegister,Ne as bridgeRestartCleanup,Re as cancelBackgroundRegisterRetries,N as getAutoRegisterConfigFilePath,Ae as getConfiguredConnectionKeys,he as getInstanceId,Ue as getRegistrationState,Ee as isRegistered,F as isTunnelConfig,T as loadConfig,R as loadConfigs,S as loadPrimaryLifecycleConfig,ye as requestRuntimeAccessTokenRefresh,Te as requestRuntimeAccessTokenRefreshForServer,Se as resolveSchedulerBaseUrlFromToken,_e as unregister};
2
2
 
@@ -38,5 +38,5 @@ export declare function hasConfiguredCredentials(): boolean;
38
38
 
39
39
  export declare function isDefaultCredentials(): boolean;
40
40
 
41
- export declare function setPanelCredentials(username: string, password: string): void;
41
+ export declare function setPanelCredentials(username: string, password: string): Promise<void>;
42
42
  export {};
@@ -1,2 +1,2 @@
1
- import{randomUUID as U,randomBytes as P,scryptSync as w,timingSafeEqual as L}from"node:crypto";import{existsSync as f,mkdirSync as N,readFileSync as l,writeFileSync as _}from"node:fs";import I from"node:os";import h from"node:path";import{logger as c}from"../utils/logger.js";const g=24*60*60*1e3,O=60*60*1e3,s=new Map;let u=null;function D(){u||(u=setInterval(()=>{const t=Date.now();let e=0;for(const[n,r]of s)t-r.lastUsedAt>g&&(s.delete(n),e++);e>0&&c.info(`[panel-auth] \u5DF2\u6E05\u7406 ${e} \u4E2A\u8FC7\u671F\u9762\u677F\u4F1A\u8BDD`)},O),u.unref())}function j(){D();const t=U(),e=Date.now();return s.set(t,{token:t,createdAt:e,lastUsedAt:e}),t}function q(t){if(!t)return!1;const e=s.get(t);if(!e)return!1;const n=Date.now();return n-e.lastUsedAt>g?(s.delete(t),!1):(e.lastUsedAt=n,!0)}function X(t){s.delete(t)}function Y(){return Array.from(s.values())}function z(){return s.size}const m="scrypt$",x=64;function d(t){return t.startsWith(m)}function E(t){const e=P(16),n=w(t,e,x);return`${m}${e.toString("hex")}$${n.toString("hex")}`}function $(t,e){if(t.length!==e.length)return!1;try{return L(Buffer.from(t,"hex"),Buffer.from(e,"hex"))}catch{return!1}}function y(t,e){if(d(e)){const n=e.split("$");if(n.length!==3)return!1;const r=Buffer.from(n[1],"hex");if(r.length===0)return!1;const o=w(t,r,x).toString("hex");return $(o,n[2])}return t===e}const k=5,p=15*60*1e3,A=10*60*1e3,i=new Map;function M(){const t=Date.now();for(const[e,n]of i)n.lockedUntil<=t&&t-n.windowStart>p&&i.delete(e)}function V(t){const e=Date.now(),n=i.get(t);return n?n.lockedUntil>e?{allowed:!1,retryAfterSec:Math.ceil((n.lockedUntil-e)/1e3)}:(e-n.windowStart>p&&i.delete(t),{allowed:!0,retryAfterSec:0}):{allowed:!0,retryAfterSec:0}}function b(t){const e=Date.now();M();const n=i.get(t);if(!n||e-n.windowStart>p){i.set(t,{failures:1,windowStart:e,lockedUntil:0});return}n.failures+=1,n.failures>=k&&(n.lockedUntil=e+A,c.warn(`[panel-auth] \u767B\u5F55\u5931\u8D25\u6B21\u6570\u8FC7\u591A\uFF0C\u5DF2\u4E34\u65F6\u9501\u5B9A\u6765\u6E90 ${t}\uFF08${A/6e4} \u5206\u949F\uFF09`))}function Q(t){i.delete(t)}let a=null;function S(){return h.join(I.homedir(),".aws-bridge","config.json")}function v(){const t=S();if(!f(t))return null;try{const e=l(t,"utf-8"),n=JSON.parse(e),r=String(n.panelUsername||"").trim(),o=String(n.panelPassword||"").trim();if(r&&o)return{username:r,password:o}}catch{}return null}function C(){if(a)return a;const t=v();return t?(a=t,t):(a={username:"root",password:"root"},a)}function Z(t,e){const n=C();if(t!==n.username||!y(e,n.password))return!1;if(!d(n.password)&&T())try{B(n.username,e),c.info("[panel-auth] \u5DF2\u5C06\u660E\u6587\u9762\u677F\u5BC6\u7801\u81EA\u52A8\u8FC1\u79FB\u4E3A scrypt \u54C8\u5E0C\u5B58\u50A8")}catch{}return!0}function F(){a=null}function T(){const t=S();if(!f(t))return!1;try{const e=l(t,"utf-8"),n=JSON.parse(e);return!!(n.panelUsername&&n.panelPassword)}catch{return!1}}function K(){const t=C();return t.username==="root"&&y("root",t.password)}function B(t,e){const n=S(),r=h.dirname(n);let o={};if(f(n))try{o=JSON.parse(l(n,"utf-8"))}catch{}o.panelUsername=t,o.panelPassword=d(e)?e:E(e);try{N(r,{recursive:!0})}catch{}_(n,JSON.stringify(o,null,2),"utf-8"),F(),c.info("[panel-auth] \u9762\u677F\u51ED\u636E\u5DF2\u66F4\u65B0")}export{V as checkLoginAllowed,F as clearCredentialsCache,j as createSession,X as destroySession,z as getActiveSessionCount,Y as getAllSessions,C as getPanelCredentials,T as hasConfiguredCredentials,K as isDefaultCredentials,b as recordLoginFailure,Q as recordLoginSuccess,B as setPanelCredentials,Z as validateCredentials,q as validateSession};
1
+ import{randomUUID as U,randomBytes as P,scryptSync as S,timingSafeEqual as L}from"node:crypto";import{existsSync as f,mkdirSync as N,readFileSync as l,writeFileSync as _}from"node:fs";import E from"node:os";import h from"node:path";import{logger as c}from"../utils/logger.js";import{runExclusiveConfigWrite as I}from"../utils/config-write-queue.js";const m=24*60*60*1e3,O=60*60*1e3,s=new Map;let u=null;function D(){u||(u=setInterval(()=>{const t=Date.now();let e=0;for(const[n,r]of s)t-r.lastUsedAt>m&&(s.delete(n),e++);e>0&&c.info(`[panel-auth] \u5DF2\u6E05\u7406 ${e} \u4E2A\u8FC7\u671F\u9762\u677F\u4F1A\u8BDD`)},O),u.unref())}function X(){D();const t=U(),e=Date.now();return s.set(t,{token:t,createdAt:e,lastUsedAt:e}),t}function Y(t){if(!t)return!1;const e=s.get(t);if(!e)return!1;const n=Date.now();return n-e.lastUsedAt>m?(s.delete(t),!1):(e.lastUsedAt=n,!0)}function z(t){s.delete(t)}function V(){return Array.from(s.values())}function b(){return s.size}const g="scrypt$",x=64;function d(t){return t.startsWith(g)}function v(t){const e=P(16),n=S(t,e,x);return`${g}${e.toString("hex")}$${n.toString("hex")}`}function $(t,e){if(t.length!==e.length)return!1;try{return L(Buffer.from(t,"hex"),Buffer.from(e,"hex"))}catch{return!1}}function y(t,e){if(d(e)){const n=e.split("$");if(n.length!==3)return!1;const r=Buffer.from(n[1],"hex");if(r.length===0)return!1;const o=S(t,r,x).toString("hex");return $(o,n[2])}return t===e}const k=5,p=15*60*1e3,A=10*60*1e3,i=new Map;function M(){const t=Date.now();for(const[e,n]of i)n.lockedUntil<=t&&t-n.windowStart>p&&i.delete(e)}function Q(t){const e=Date.now(),n=i.get(t);return n?n.lockedUntil>e?{allowed:!1,retryAfterSec:Math.ceil((n.lockedUntil-e)/1e3)}:(e-n.windowStart>p&&i.delete(t),{allowed:!0,retryAfterSec:0}):{allowed:!0,retryAfterSec:0}}function Z(t){const e=Date.now();M();const n=i.get(t);if(!n||e-n.windowStart>p){i.set(t,{failures:1,windowStart:e,lockedUntil:0});return}n.failures+=1,n.failures>=k&&(n.lockedUntil=e+A,c.warn(`[panel-auth] \u767B\u5F55\u5931\u8D25\u6B21\u6570\u8FC7\u591A\uFF0C\u5DF2\u4E34\u65F6\u9501\u5B9A\u6765\u6E90 ${t}\uFF08${A/6e4} \u5206\u949F\uFF09`))}function K(t){i.delete(t)}let a=null;function w(){return h.join(E.homedir(),".aws-bridge","config.json")}function F(){const t=w();if(!f(t))return null;try{const e=l(t,"utf-8"),n=JSON.parse(e),r=String(n.panelUsername||"").trim(),o=String(n.panelPassword||"").trim();if(r&&o)return{username:r,password:o}}catch{}return null}function C(){if(a)return a;const t=F();return t?(a=t,t):(a={username:"root",password:"root"},a)}function tt(t,e){const n=C();return t!==n.username||!y(e,n.password)?!1:(!d(n.password)&&B()&&H(n.username,e).then(()=>{c.info("[panel-auth] \u5DF2\u5C06\u660E\u6587\u9762\u677F\u5BC6\u7801\u81EA\u52A8\u8FC1\u79FB\u4E3A scrypt \u54C8\u5E0C\u5B58\u50A8")}).catch(()=>{}),!0)}function T(){a=null}function B(){const t=w();if(!f(t))return!1;try{const e=l(t,"utf-8"),n=JSON.parse(e);return!!(n.panelUsername&&n.panelPassword)}catch{return!1}}function et(){const t=C();return t.username==="root"&&y("root",t.password)}async function H(t,e){const n=w();T(),await I(n,()=>{const r=h.dirname(n);let o={};if(f(n))try{o=JSON.parse(l(n,"utf-8"))}catch{}o.panelUsername=t,o.panelPassword=d(e)?e:v(e);try{N(r,{recursive:!0})}catch{}_(n,JSON.stringify(o,null,2),"utf-8"),c.info("[panel-auth] \u9762\u677F\u51ED\u636E\u5DF2\u66F4\u65B0")})}export{Q as checkLoginAllowed,T as clearCredentialsCache,X as createSession,z as destroySession,b as getActiveSessionCount,V as getAllSessions,C as getPanelCredentials,B as hasConfiguredCredentials,et as isDefaultCredentials,Z as recordLoginFailure,K as recordLoginSuccess,H as setPanelCredentials,tt as validateCredentials,Y as validateSession};
2
2
 
@@ -1,2 +1,2 @@
1
- import{execFileSync as i,execSync as w}from"node:child_process";import c from"node:os";import{createLogger as T}from"../utils/logger.js";const u=T("system-metrics"),S=120,N=1024**3;let p=null;function y(t){return Math.round(t*100)/100}function d(t){return Number.isFinite(t)?y(Math.min(100,Math.max(0,t))):0}function B(t){return y(t/N)}function f(){return c.cpus().reduce((t,e)=>{const o=Object.values(e.times).reduce((n,r)=>n+r,0);return{idle:t.idle+e.times.idle,total:t.total+o}},{idle:0,total:0})}function g(t){return new Promise(e=>{setTimeout(e,t)})}async function b(){const t=f();await g(S);const e=f(),o=e.total-t.total,n=e.idle-t.idle,r=o<=0?0:(o-n)/o*100,s=c.cpus();return{usagePercent:d(r),coreCount:s.length,model:s[0]?.model||c.arch()}}function h(){const t=c.totalmem(),e=c.freemem(),o=Math.max(0,t-e);return{usedBytes:o,freeBytes:e,totalBytes:t,usagePercent:t>0?d(o/t*100):0}}function P(){return process.env.AWS_SYSTEM_METRICS_STORAGE_PATH||process.cwd()}function M(t){const o=`Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='${(c.platform()==="win32"?t.slice(0,2).toUpperCase():t).replace(/'/g,"''")}'" | Select-Object Size,FreeSpace | ConvertTo-Json -Compress`,n=i("powershell",["-NoProfile","-NonInteractive","-Command",o],{encoding:"utf8",timeout:5e3,windowsHide:!0}).trim(),r=JSON.parse(n);return{freeBytes:Number(r.FreeSpace||0),totalBytes:Number(r.Size||0)}}function v(t){const n=i("df",["-kP",t],{encoding:"utf8",timeout:5e3}).trim().split(/\r?\n/).filter(Boolean)[1]?.trim().split(/\s+/)||[],r=Number(n[1]||0),s=Number(n[2]||0);return{freeBytes:Number(n[3]||Math.max(0,r-s))*1024,totalBytes:r*1024}}function k(){const t=P();try{const e=c.platform()==="win32"?M(t):v(t),o=Math.max(0,e.totalBytes),n=Math.max(0,e.freeBytes),r=Math.max(0,o-n);return{path:t,usedBytes:r,freeBytes:n,totalBytes:o,usagePercent:o>0?d(r/o*100):0,freeGiB:B(n),totalGiB:B(o)}}catch(e){return u.debug("Failed to collect storage metrics:",e),{path:t,usedBytes:0,freeBytes:0,totalBytes:0,usagePercent:0,freeGiB:0,totalGiB:0}}}function x(){try{const t=w("cat /proc/net/dev",{encoding:"utf8",timeout:5e3});let e=0,o=0;for(const n of t.split(/\r?\n/)){const[r,s]=n.split(":");if(!r||!s)continue;const l=r.trim();if(!l||l==="lo")continue;const a=s.trim().split(/\s+/).map(Number);o+=Number.isFinite(a[0])?a[0]:0,e+=Number.isFinite(a[8])?a[8]:0}return{uploadTotalBytes:e,downloadTotalBytes:o}}catch(t){return u.debug("Failed to collect Linux network metrics:",t),null}}function C(){try{const t=i("netstat",["-ibn"],{encoding:"utf8",timeout:5e3}),e=new Set;let o=0,n=0;for(const r of t.split(/\r?\n/).slice(1)){const s=r.trim().split(/\s+/),l=s[0];if(!l||l==="lo0"||e.has(l))continue;const a=Number(s[6]||0),m=Number(s[9]||0);n+=Number.isFinite(a)?a:0,o+=Number.isFinite(m)?m:0,e.add(l)}return{uploadTotalBytes:o,downloadTotalBytes:n}}catch(t){return u.debug("Failed to collect macOS network metrics:",t),null}}function F(){try{const e=i("powershell",["-NoProfile","-NonInteractive","-Command","Get-NetAdapterStatistics | Select-Object ReceivedBytes,SentBytes | ConvertTo-Json -Compress"],{encoding:"utf8",timeout:5e3,windowsHide:!0}).trim();if(!e)return null;const o=JSON.parse(e);return(Array.isArray(o)?o:[o]).reduce((r,s)=>({downloadTotalBytes:r.downloadTotalBytes+Number(s.ReceivedBytes||0),uploadTotalBytes:r.uploadTotalBytes+Number(s.SentBytes||0)}),{uploadTotalBytes:0,downloadTotalBytes:0})}catch(t){return u.debug("Failed to collect Windows network metrics:",t),null}}function A(){const t=c.platform(),e=t==="win32"?F():t==="darwin"?C():x();return{uploadTotalBytes:e?.uploadTotalBytes||0,downloadTotalBytes:e?.downloadTotalBytes||0,sampledAt:Date.now()}}function D(){const t=A(),e=p;if(p=t,!e)return{uploadBytesPerSecond:0,downloadBytesPerSecond:0,uploadTotalBytes:t.uploadTotalBytes,downloadTotalBytes:t.downloadTotalBytes};const o=Math.max(1,(t.sampledAt-e.sampledAt)/1e3);return{uploadBytesPerSecond:Math.max(0,Math.round((t.uploadTotalBytes-e.uploadTotalBytes)/o)),downloadBytesPerSecond:Math.max(0,Math.round((t.downloadTotalBytes-e.downloadTotalBytes)/o)),uploadTotalBytes:t.uploadTotalBytes,downloadTotalBytes:t.downloadTotalBytes}}async function O(){const[t,e,o,n]=await Promise.all([b(),Promise.resolve(h()),Promise.resolve(k()),Promise.resolve(D())]);return{ok:!0,collectedAt:new Date().toISOString(),cpu:t,memory:e,storage:o,network:n}}export{O as collectSystemMetrics};
1
+ import{execFile as T,exec as S}from"node:child_process";import{promisify as y}from"node:util";import c from"node:os";const i=y(T),N=y(S);import{createLogger as b}from"../utils/logger.js";const u=b("system-metrics"),g=120,h=1024**3;let p=null;function B(t){return Math.round(t*100)/100}function d(t){return Number.isFinite(t)?B(Math.min(100,Math.max(0,t))):0}function w(t){return B(t/h)}function f(){return c.cpus().reduce((t,e)=>{const o=Object.values(e.times).reduce((n,s)=>n+s,0);return{idle:t.idle+e.times.idle,total:t.total+o}},{idle:0,total:0})}function M(t){return new Promise(e=>{setTimeout(e,t)})}async function P(){const t=f();await M(g);const e=f(),o=e.total-t.total,n=e.idle-t.idle,s=o<=0?0:(o-n)/o*100,r=c.cpus();return{usagePercent:d(s),coreCount:r.length,model:r[0]?.model||c.arch()}}function x(){const t=c.totalmem(),e=c.freemem(),o=Math.max(0,t-e);return{usedBytes:o,freeBytes:e,totalBytes:t,usagePercent:t>0?d(o/t*100):0}}function k(){return process.env.AWS_SYSTEM_METRICS_STORAGE_PATH||process.cwd()}async function C(t){const o=`Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='${(c.platform()==="win32"?t.slice(0,2).toUpperCase():t).replace(/'/g,"''")}'" | Select-Object Size,FreeSpace | ConvertTo-Json -Compress`,{stdout:n}=await i("powershell",["-NoProfile","-NonInteractive","-Command",o],{encoding:"utf8",timeout:5e3,windowsHide:!0}),s=JSON.parse(n.trim());return{freeBytes:Number(s.FreeSpace||0),totalBytes:Number(s.Size||0)}}async function F(t){const{stdout:e}=await i("df",["-kP",t],{encoding:"utf8",timeout:5e3}),n=e.trim().split(/\r?\n/).filter(Boolean)[1]?.trim().split(/\s+/)||[],s=Number(n[1]||0),r=Number(n[2]||0);return{freeBytes:Number(n[3]||Math.max(0,s-r))*1024,totalBytes:s*1024}}async function v(){const t=k();try{const e=c.platform()==="win32"?await C(t):await F(t),o=Math.max(0,e.totalBytes),n=Math.max(0,e.freeBytes),s=Math.max(0,o-n);return{path:t,usedBytes:s,freeBytes:n,totalBytes:o,usagePercent:o>0?d(s/o*100):0,freeGiB:w(n),totalGiB:w(o)}}catch(e){return u.debug("Failed to collect storage metrics:",e),{path:t,usedBytes:0,freeBytes:0,totalBytes:0,usagePercent:0,freeGiB:0,totalGiB:0}}}async function A(){try{const{stdout:t}=await N("cat /proc/net/dev",{encoding:"utf8",timeout:5e3});let e=0,o=0;for(const n of t.split(/\r?\n/)){const[s,r]=n.split(":");if(!s||!r)continue;const a=s.trim();if(!a||a==="lo")continue;const l=r.trim().split(/\s+/).map(Number);o+=Number.isFinite(l[0])?l[0]:0,e+=Number.isFinite(l[8])?l[8]:0}return{uploadTotalBytes:e,downloadTotalBytes:o}}catch(t){return u.debug("Failed to collect Linux network metrics:",t),null}}async function D(){try{const{stdout:t}=await i("netstat",["-ibn"],{encoding:"utf8",timeout:5e3}),e=new Set;let o=0,n=0;for(const s of t.split(/\r?\n/).slice(1)){const r=s.trim().split(/\s+/),a=r[0];if(!a||a==="lo0"||e.has(a))continue;const l=Number(r[6]||0),m=Number(r[9]||0);n+=Number.isFinite(l)?l:0,o+=Number.isFinite(m)?m:0,e.add(a)}return{uploadTotalBytes:o,downloadTotalBytes:n}}catch(t){return u.debug("Failed to collect macOS network metrics:",t),null}}async function _(){try{const t="Get-NetAdapterStatistics | Select-Object ReceivedBytes,SentBytes | ConvertTo-Json -Compress",{stdout:e}=await i("powershell",["-NoProfile","-NonInteractive","-Command",t],{encoding:"utf8",timeout:5e3,windowsHide:!0}),o=e.trim();if(!o)return null;const n=JSON.parse(o);return(Array.isArray(n)?n:[n]).reduce((r,a)=>({downloadTotalBytes:r.downloadTotalBytes+Number(a.ReceivedBytes||0),uploadTotalBytes:r.uploadTotalBytes+Number(a.SentBytes||0)}),{uploadTotalBytes:0,downloadTotalBytes:0})}catch(t){return u.debug("Failed to collect Windows network metrics:",t),null}}async function G(){const t=c.platform(),e=t==="win32"?await _():t==="darwin"?await D():await A();return{uploadTotalBytes:e?.uploadTotalBytes||0,downloadTotalBytes:e?.downloadTotalBytes||0,sampledAt:Date.now()}}async function I(){const t=await G(),e=p;if(p=t,!e)return{uploadBytesPerSecond:0,downloadBytesPerSecond:0,uploadTotalBytes:t.uploadTotalBytes,downloadTotalBytes:t.downloadTotalBytes};const o=Math.max(1,(t.sampledAt-e.sampledAt)/1e3);return{uploadBytesPerSecond:Math.max(0,Math.round((t.uploadTotalBytes-e.uploadTotalBytes)/o)),downloadBytesPerSecond:Math.max(0,Math.round((t.downloadTotalBytes-e.downloadTotalBytes)/o)),uploadTotalBytes:t.uploadTotalBytes,downloadTotalBytes:t.downloadTotalBytes}}async function U(){const[t,e,o,n]=await Promise.all([P(),Promise.resolve(x()),v(),I()]);return{ok:!0,collectedAt:new Date().toISOString(),cpu:t,memory:e,storage:o,network:n}}export{U as collectSystemMetrics};
2
2
 
@@ -3,3 +3,7 @@
3
3
  export declare function startTunnelClient(serverUrl: string, instanceId: string): void;
4
4
 
5
5
  export declare function stopTunnelClient(): void;
6
+
7
+ export declare function stopTunnelClientForServer(serverUrl: string): void;
8
+
9
+ export declare function stopTunnelClientsExcept(serverUrls: string[]): void;
@@ -1,2 +1,2 @@
1
- import R,{AxiosError as M}from"axios";import h from"ws";import{getRuntimeAccessToken as L,normalizeSchedulerBaseUrl as E}from"./runtime-binding.js";import{loadConfigs as O,loadPrimaryLifecycleConfig as W}from"./auto-register.js";import{logger as c}from"../utils/logger.js";import{port as k}from"../config.js";const x=15e3,D=5e3,P=200,$=1*1024*1024,q=4001,G=768*1024,J=384*1024,g=new Map;function K(e){const r=e.serverUrl,n=e.instanceId;if(!r||!n)return null;try{const t=new URL(r),o=t.protocol==="https:"?"wss:":"ws:",i=L(r),l=O().find(m=>E(m.serverUrl)===r)||W(),s=i||l.userKey;if(i?c.debug("[TunnelClient] \u4F7F\u7528 runtime access token \u8FDE\u63A5\u96A7\u9053"):l.userKey&&c.warn("[TunnelClient] getRuntimeAccessToken \u8FD4\u56DE\u7A7A\uFF0C\u56DE\u9000\u5230 userKey\uFF08\u975E runtime token\uFF0C\u9274\u6743\u53EF\u80FD\u5931\u8D25\uFF09: targetUrl=",r),!s)return c.warn("[TunnelClient] \u7F3A\u5C11 runtime token\uFF0C\u65E0\u6CD5\u5EFA\u7ACB\u96A7\u9053: targetUrl=",r),null;const a=new URLSearchParams({instanceId:n});return{url:`${o}//${t.host}/ws/bridge-tunnel?${a.toString()}`,token:s}}catch(t){return c.warn("[TunnelClient] \u65E0\u6CD5\u89E3\u6790 serverUrl:",String(t)),null}}function re(e,r){const n=E(e);if(!n){c.warn("[TunnelClient] serverUrl \u65E0\u6548\uFF0C\u65E0\u6CD5\u542F\u52A8\u96A7\u9053:",e);return}const t=g.get(n);t&&(t.shuttingDown=!0,v(t),g.delete(n));const o={serverUrl:n,instanceId:r,streams:new Map,shuttingDown:!1};g.set(n,o),B(o)}function te(){for(const e of g.values())e.shuttingDown=!0,v(e);g.clear()}function v(e){e.timer&&(clearInterval(e.timer),e.timer=void 0),e.reconnectTimer&&(clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0);for(const[r,n]of e.streams.entries()){try{n.ws.close(1e3,"tunnel client stopped")}catch{}e.streams.delete(r)}if(e.ws){e.ws.removeAllListeners();try{e.ws.close(1e3,"tunnel client stopped")}catch{}e.ws=void 0}}function B(e){if(e.shuttingDown)return;const r=e.instanceId;if(!r)return;const n=K(e);if(!n){y(e);return}c.info("[TunnelClient] \u8FDE\u63A5\u96A7\u9053\u670D\u52A1\u7AEF:",n.url);const t=new h(n.url,{handshakeTimeout:15e3,headers:{"X-Runtime-Token":n.token}});e.ws=t,t.on("open",()=>{c.info("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u5DF2\u5EFA\u7ACB: server=",e.serverUrl,"instanceId=",r),e.reconnectTimer&&clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0,e.timer=setInterval(()=>{t.readyState===h.OPEN&&t.send(JSON.stringify({t:"ping"}))},x)}),t.on("unexpected-response",(o,i)=>{c.error("[TunnelClient] \u670D\u52A1\u7AEF\u62D2\u7EDD\u8FDE\u63A5\uFF08\u975E 101 \u54CD\u5E94\uFF09: server=",e.serverUrl,"statusCode=",i.statusCode,"statusMessage=",i.statusMessage)}),t.on("upgrade",o=>{c.debug("[TunnelClient] \u6536\u5230\u5347\u7EA7\u54CD\u5E94: statusCode=",o.statusCode)}),t.on("message",o=>{let i;typeof o=="string"?i=o:Buffer.isBuffer(o)?i=o.toString("utf-8"):i=Buffer.from(o).toString("utf-8");try{const l=JSON.parse(i);H(e,l)}catch(l){c.warn("[TunnelClient] \u89E3\u6790\u96A7\u9053\u6D88\u606F\u5931\u8D25:",String(l))}}),t.on("close",(o,i)=>{if(c.warn("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u5173\u95ED: server=",e.serverUrl,"code=",o,"reason=",String(i)),_(e),o===q){c.warn("[TunnelClient] \u9274\u6743\u5931\u8D25\uFF084001\uFF09\uFF0C\u505C\u6B62\u81EA\u52A8\u91CD\u8FDE\uFF1B\u5F85\u91CD\u65B0\u6CE8\u518C\u540E\u518D\u542F\u52A8\u96A7\u9053: server=",e.serverUrl);return}y(e)}),t.on("error",o=>{c.warn("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u9519\u8BEF: server=",e.serverUrl,"err=",o.message,"code=",o.code),_(e),y(e)})}function _(e){e.timer&&(clearInterval(e.timer),e.timer=void 0);for(const[r,n]of e.streams.entries()){try{n.ws.close(1011,"tunnel disconnected")}catch{}e.streams.delete(r)}e.ws=void 0}function y(e){e.shuttingDown||e.reconnectTimer||(e.reconnectTimer=setTimeout(()=>{e.reconnectTimer=void 0,B(e)},D))}function H(e,r){switch(r.t){case"req":X(e,r);break;case"wsopen":Y(e,r);break;case"wsmsg":j(e,r);break;case"wsclose":z(e,r);break;case"ping":f(e,{t:"pong"});break;case"pong":break;default:c.debug("[TunnelClient] \u672A\u77E5\u6D88\u606F\u7C7B\u578B: t=",r.t)}}async function X(e,r){const n=r.id,t=r.method||"GET",o=r.path||"/",i=`http://127.0.0.1:${k}${o}`;try{const l=r.body?Buffer.from(r.body,"base64"):void 0,s={};if(r.headers)for(const[d,u]of Object.entries(r.headers))u&&u.length>0&&(s[d]=u[0]);s["x-bridge-tunnel-source"]=e.serverUrl;const a=await R.request({url:i,method:t,headers:s,data:l,timeout:0,responseType:"stream",validateStatus:()=>!0}),m={};for(const[d,u]of Object.entries(a.headers))u!==void 0&&(m[d]=Array.isArray(u)?u:[String(u)]);const b=a.data,A=String(m["content-type"]?.[0]||"").toLowerCase().includes("text/event-stream"),p=[];let w=0,T=!1,S=!1;try{if(b)for await(const d of b){const u=Buffer.isBuffer(d)?d:Buffer.from(d);if(u.length!==0){if(T){if(!C(e,n,u))return}else if(A){if(T=!0,S=!0,!f(e,{t:"res",id:n,status:a.status,headers:m,body:null,chunked:!0})||!C(e,n,u))return}else if(p.push(u),w+=u.length,w>G){if(T=!0,S=!0,!f(e,{t:"res",id:n,status:a.status,headers:m,body:null,chunked:!0}))return;for(const N of p)if(!C(e,n,N))return;p.length=0,w=0}}}}catch(d){const u=d instanceof Error?d.message:String(d);c.warn("[TunnelClient] \u672C\u5730\u6D41\u8BFB\u53D6\u4E2D\u65AD:",t,o,"->",u),S?f(e,{t:"resend",id:n,error:u}):f(e,{t:"res",id:n,status:502,headers:{"content-type":["text/plain; charset=utf-8"]},body:Buffer.from(u).toString("base64")});return}if(T){f(e,{t:"resend",id:n});return}const I=w>0?Buffer.concat(p).toString("base64"):"";f(e,{t:"res",id:n,status:a.status,headers:m,body:I||null})}catch(l){const s=l instanceof M?l.message:String(l);c.warn("[TunnelClient] \u672C\u5730\u8F6C\u53D1\u5931\u8D25:",t,o,"->",s),f(e,{t:"res",id:n,status:502,headers:{"content-type":["text/plain; charset=utf-8"]},body:Buffer.from(s).toString("base64")})}}function C(e,r,n){let t=0;for(;t<n.length;){const o=n.subarray(t,t+J);if(!f(e,{t:"reschunk",id:r,d:o.toString("base64")}))return!1;t+=o.length}return!0}function Y(e,r){const n=r.id,t=r.path||"/",o=`ws://127.0.0.1:${k}${t}`;let i;try{i=new h(o,{handshakeTimeout:1e4})}catch(s){c.warn("[TunnelClient] \u672C\u5730 WS \u8FDE\u63A5\u5931\u8D25:",String(s)),f(e,{t:"wsclose",id:n,code:1011,reason:"local ws connect failed"});return}const l={ws:i,pending:[]};e.streams.set(n,l),i.on("open",()=>{for(const s of l.pending)U(l,s.data,s.bin);l.pending=[],f(e,{t:"wsopened",id:n})}),i.on("message",(s,a)=>{const m=Buffer.isBuffer(s)?s:Buffer.from(s);a&&e.ws&&e.ws.bufferedAmount>$||f(e,{t:"wsmsg",id:n,bin:a,d:m.toString("base64")})}),i.on("close",(s,a)=>{e.streams.delete(n),f(e,{t:"wsclose",id:n,code:s,reason:String(a)})}),i.on("error",s=>{c.warn("[TunnelClient] \u672C\u5730 WS \u9519\u8BEF: id=",n,"err=",s.message),e.streams.delete(n);try{i.close(1011,s.message)}catch{}f(e,{t:"wsclose",id:n,code:1011,reason:s.message})})}function U(e,r,n){n?e.ws.send(r):e.ws.send(r.toString("utf-8"))}function j(e,r){const n=e.streams.get(r.id);if(!n)return;const t=r.d?Buffer.from(r.d,"base64"):Buffer.alloc(0);if(n.ws.readyState!==h.OPEN){n.pending.length>=P&&(n.pending.shift(),c.warn("[TunnelClient] \u672C\u5730 WS \u672A\u5C31\u7EEA\uFF0C\u6392\u961F\u6D88\u606F\u8D85\u4E0A\u9650\uFF0C\u4E22\u5F03\u6700\u65E7\u4E00\u6761: id=",r.id)),n.pending.push({data:t,bin:!!r.bin});return}U(n,t,!!r.bin)}function z(e,r){const n=e.streams.get(r.id);if(n){e.streams.delete(r.id);try{n.ws.close(r.code??1e3,r.reason||"")}catch{}}}function f(e,r){if(!e.ws||e.ws.readyState!==h.OPEN)return!1;try{return e.ws.send(JSON.stringify(r)),!0}catch(n){return c.warn("[TunnelClient] sendJson \u5931\u8D25:",String(n)),!1}}export{re as startTunnelClient,te as stopTunnelClient};
1
+ import R,{AxiosError as x}from"axios";import p from"ws";import{getRuntimeAccessToken as M,normalizeSchedulerBaseUrl as S}from"./runtime-binding.js";import{loadConfigs as L,loadPrimaryLifecycleConfig as O}from"./auto-register.js";import{logger as c}from"../utils/logger.js";import{port as v}from"../config.js";const D=15e3,W=5e3,P=200,$=1*1024*1024,q=4001,G=768*1024,J=384*1024,m=new Map;function K(e){const r=e.serverUrl,n=e.instanceId;if(!r||!n)return null;try{const t=new URL(r),o=t.protocol==="https:"?"wss:":"ws:",i=M(r),l=L().find(h=>S(h.serverUrl)===r)||O(),s=i||l.userKey;if(i?c.debug("[TunnelClient] \u4F7F\u7528 runtime access token \u8FDE\u63A5\u96A7\u9053"):l.userKey&&c.warn("[TunnelClient] getRuntimeAccessToken \u8FD4\u56DE\u7A7A\uFF0C\u56DE\u9000\u5230 userKey\uFF08\u975E runtime token\uFF0C\u9274\u6743\u53EF\u80FD\u5931\u8D25\uFF09: targetUrl=",r),!s)return c.warn("[TunnelClient] \u7F3A\u5C11 runtime token\uFF0C\u65E0\u6CD5\u5EFA\u7ACB\u96A7\u9053: targetUrl=",r),null;const a=new URLSearchParams({instanceId:n});return{url:`${o}//${t.host}/ws/bridge-tunnel?${a.toString()}`,token:s}}catch(t){return c.warn("[TunnelClient] \u65E0\u6CD5\u89E3\u6790 serverUrl:",String(t)),null}}function re(e,r){const n=S(e);if(!n){c.warn("[TunnelClient] serverUrl \u65E0\u6548\uFF0C\u65E0\u6CD5\u542F\u52A8\u96A7\u9053:",e);return}const t=m.get(n);t&&(t.shuttingDown=!0,C(t),m.delete(n));const o={serverUrl:n,instanceId:r,streams:new Map,shuttingDown:!1};m.set(n,o),B(o)}function te(){for(const e of m.values())e.shuttingDown=!0,C(e);m.clear()}function oe(e){const r=S(e);if(!r)return;const n=m.get(r);n&&(n.shuttingDown=!0,C(n),m.delete(r),c.info(`[TunnelClient] tunnel stopped: ${r}`))}function se(e){const r=new Set(e.map(n=>S(n)).filter(n=>!!n));for(const[n,t]of m.entries())r.has(n)||(c.info(`[TunnelClient] serverUrl \u5DF2\u4E0D\u5728\u914D\u7F6E\u4E2D\uFF0C\u505C\u6B62\u65E7\u96A7\u9053: ${n}`),t.shuttingDown=!0,C(t),m.delete(n))}function C(e){e.timer&&(clearInterval(e.timer),e.timer=void 0),e.reconnectTimer&&(clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0);for(const[r,n]of e.streams.entries()){try{n.ws.close(1e3,"tunnel client stopped")}catch{}e.streams.delete(r)}if(e.ws){e.ws.removeAllListeners();try{e.ws.close(1e3,"tunnel client stopped")}catch{}e.ws=void 0}}function B(e){if(e.shuttingDown)return;const r=e.instanceId;if(!r)return;const n=K(e);if(!n){b(e);return}c.info("[TunnelClient] \u8FDE\u63A5\u96A7\u9053\u670D\u52A1\u7AEF:",n.url);const t=new p(n.url,{handshakeTimeout:15e3,headers:{"X-Runtime-Token":n.token}});e.ws=t,t.on("open",()=>{c.info("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u5DF2\u5EFA\u7ACB: server=",e.serverUrl,"instanceId=",r),e.reconnectTimer&&clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0,e.timer=setInterval(()=>{t.readyState===p.OPEN&&t.send(JSON.stringify({t:"ping"}))},D)}),t.on("unexpected-response",(o,i)=>{c.error("[TunnelClient] \u670D\u52A1\u7AEF\u62D2\u7EDD\u8FDE\u63A5\uFF08\u975E 101 \u54CD\u5E94\uFF09: server=",e.serverUrl,"statusCode=",i.statusCode,"statusMessage=",i.statusMessage)}),t.on("upgrade",o=>{c.debug("[TunnelClient] \u6536\u5230\u5347\u7EA7\u54CD\u5E94: statusCode=",o.statusCode)}),t.on("message",o=>{let i;typeof o=="string"?i=o:Buffer.isBuffer(o)?i=o.toString("utf-8"):i=Buffer.from(o).toString("utf-8");try{const l=JSON.parse(i);H(e,l)}catch(l){c.warn("[TunnelClient] \u89E3\u6790\u96A7\u9053\u6D88\u606F\u5931\u8D25:",String(l))}}),t.on("close",(o,i)=>{if(c.warn("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u5173\u95ED: server=",e.serverUrl,"code=",o,"reason=",String(i)),U(e),o===q){c.warn("[TunnelClient] \u9274\u6743\u5931\u8D25\uFF084001\uFF09\uFF0C\u505C\u6B62\u81EA\u52A8\u91CD\u8FDE\uFF1B\u5F85\u91CD\u65B0\u6CE8\u518C\u540E\u518D\u542F\u52A8\u96A7\u9053: server=",e.serverUrl);return}b(e)}),t.on("error",o=>{c.warn("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u9519\u8BEF: server=",e.serverUrl,"err=",o.message,"code=",o.code),U(e),b(e)})}function U(e){e.timer&&(clearInterval(e.timer),e.timer=void 0);for(const[r,n]of e.streams.entries()){try{n.ws.close(1011,"tunnel disconnected")}catch{}e.streams.delete(r)}e.ws=void 0}function b(e){e.shuttingDown||e.reconnectTimer||(e.reconnectTimer=setTimeout(()=>{e.reconnectTimer=void 0,B(e)},W))}function H(e,r){switch(r.t){case"req":z(e,r);break;case"wsopen":F(e,r);break;case"wsmsg":X(e,r);break;case"wsclose":Y(e,r);break;case"ping":f(e,{t:"pong"});break;case"pong":break;default:c.debug("[TunnelClient] \u672A\u77E5\u6D88\u606F\u7C7B\u578B: t=",r.t)}}async function z(e,r){const n=r.id,t=r.method||"GET",o=r.path||"/",i=`http://127.0.0.1:${v}${o}`;try{const l=r.body?Buffer.from(r.body,"base64"):void 0,s={};if(r.headers)for(const[d,u]of Object.entries(r.headers))u&&u.length>0&&(s[d]=u[0]);s["x-bridge-tunnel-source"]=e.serverUrl;const a=await R.request({url:i,method:t,headers:s,data:l,timeout:0,responseType:"stream",validateStatus:()=>!0}),h={};for(const[d,u]of Object.entries(a.headers))u!==void 0&&(h[d]=Array.isArray(u)?u:[String(u)]);const k=a.data,A=String(h["content-type"]?.[0]||"").toLowerCase().includes("text/event-stream"),g=[];let w=0,T=!1,y=!1;try{if(k)for await(const d of k){const u=Buffer.isBuffer(d)?d:Buffer.from(d);if(u.length!==0){if(T){if(!E(e,n,u))return}else if(A){if(T=!0,y=!0,!f(e,{t:"res",id:n,status:a.status,headers:h,body:null,chunked:!0})||!E(e,n,u))return}else if(g.push(u),w+=u.length,w>G){if(T=!0,y=!0,!f(e,{t:"res",id:n,status:a.status,headers:h,body:null,chunked:!0}))return;for(const N of g)if(!E(e,n,N))return;g.length=0,w=0}}}}catch(d){const u=d instanceof Error?d.message:String(d);c.warn("[TunnelClient] \u672C\u5730\u6D41\u8BFB\u53D6\u4E2D\u65AD:",t,o,"->",u),y?f(e,{t:"resend",id:n,error:u}):f(e,{t:"res",id:n,status:502,headers:{"content-type":["text/plain; charset=utf-8"]},body:Buffer.from(u).toString("base64")});return}if(T){f(e,{t:"resend",id:n});return}const I=w>0?Buffer.concat(g).toString("base64"):"";f(e,{t:"res",id:n,status:a.status,headers:h,body:I||null})}catch(l){const s=l instanceof x?l.message:String(l);c.warn("[TunnelClient] \u672C\u5730\u8F6C\u53D1\u5931\u8D25:",t,o,"->",s),f(e,{t:"res",id:n,status:502,headers:{"content-type":["text/plain; charset=utf-8"]},body:Buffer.from(s).toString("base64")})}}function E(e,r,n){let t=0;for(;t<n.length;){const o=n.subarray(t,t+J);if(!f(e,{t:"reschunk",id:r,d:o.toString("base64")}))return!1;t+=o.length}return!0}function F(e,r){const n=r.id,t=r.path||"/",o=`ws://127.0.0.1:${v}${t}`;let i;try{i=new p(o,{handshakeTimeout:1e4})}catch(s){c.warn("[TunnelClient] \u672C\u5730 WS \u8FDE\u63A5\u5931\u8D25:",String(s)),f(e,{t:"wsclose",id:n,code:1011,reason:"local ws connect failed"});return}const l={ws:i,pending:[]};e.streams.set(n,l),i.on("open",()=>{for(const s of l.pending)_(l,s.data,s.bin);l.pending=[],f(e,{t:"wsopened",id:n})}),i.on("message",(s,a)=>{const h=Buffer.isBuffer(s)?s:Buffer.from(s);a&&e.ws&&e.ws.bufferedAmount>$||f(e,{t:"wsmsg",id:n,bin:a,d:h.toString("base64")})}),i.on("close",(s,a)=>{e.streams.delete(n),f(e,{t:"wsclose",id:n,code:s,reason:String(a)})}),i.on("error",s=>{c.warn("[TunnelClient] \u672C\u5730 WS \u9519\u8BEF: id=",n,"err=",s.message),e.streams.delete(n);try{i.close(1011,s.message)}catch{}f(e,{t:"wsclose",id:n,code:1011,reason:s.message})})}function _(e,r,n){n?e.ws.send(r):e.ws.send(r.toString("utf-8"))}function X(e,r){const n=e.streams.get(r.id);if(!n)return;const t=r.d?Buffer.from(r.d,"base64"):Buffer.alloc(0);if(n.ws.readyState!==p.OPEN){n.pending.length>=P&&(n.pending.shift(),c.warn("[TunnelClient] \u672C\u5730 WS \u672A\u5C31\u7EEA\uFF0C\u6392\u961F\u6D88\u606F\u8D85\u4E0A\u9650\uFF0C\u4E22\u5F03\u6700\u65E7\u4E00\u6761: id=",r.id)),n.pending.push({data:t,bin:!!r.bin});return}_(n,t,!!r.bin)}function Y(e,r){const n=e.streams.get(r.id);if(n){e.streams.delete(r.id);try{n.ws.close(r.code??1e3,r.reason||"")}catch{}}}function f(e,r){if(!e.ws||e.ws.readyState!==p.OPEN)return!1;try{return e.ws.send(JSON.stringify(r)),!0}catch(n){return c.warn("[TunnelClient] sendJson \u5931\u8D25:",String(n)),!1}}export{re as startTunnelClient,te as stopTunnelClient,oe as stopTunnelClientForServer,se as stopTunnelClientsExcept};
2
2
 
@@ -0,0 +1,3 @@
1
+
2
+
3
+ export declare function runExclusiveConfigWrite<T>(filePath: string, task: () => T | Promise<T>): Promise<T>;
@@ -0,0 +1,2 @@
1
+ const s=new Map;function o(e,t){const n=(s.get(e)??Promise.resolve()).then(()=>t());return s.set(e,n.then(()=>{},()=>{})),n}export{o as runExclusiveConfigWrite};
2
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-runtime-bridge",
3
- "version": "1.9.122",
3
+ "version": "1.9.124",
4
4
  "description": "AgentsWorkStudio runtime bridge service for machine-level agent runtime integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",