@typhons/sandbox-tools 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ipc.js CHANGED
@@ -1 +1 @@
1
- import l from"net";import a from"fs";var S="/tmp/session-proxy.sock",y=2*1024*1024,h=class{constructor(t=y){this.capacity=t,this.chunks=[],this.totalBytes=0,this.trimmedTo=0}append(t){let e=this.totalBytes;return this.chunks.push({offset:e,data:Buffer.from(t)}),this.totalBytes+=t.length,this._trim(),e}sliceFrom(t){if(t>=this.totalBytes)return Buffer.alloc(0);if(t<this.trimmedTo)throw new Error(`RING_OVERFLOW: requested offset ${t} already trimmed (trimmedTo=${this.trimmedTo})`);let e=[];for(let n of this.chunks){if(n.offset+n.data.length<=t)continue;let i=Math.max(0,t-n.offset);e.push(n.data.slice(i))}return Buffer.concat(e)}_trim(){for(;this.chunks.length>1&&!(this.totalBytes-this.chunks[0].offset<=this.capacity);){let e=this.chunks.shift();this.trimmedTo=e.offset+e.data.length}}},d=1,m=2;function x(r,t){if(!r||r.destroyed)return!1;let e=Buffer.alloc(5);return e[0]=d,e.writeUInt32LE(t.length,1),r.write(e),r.write(t),!0}function w(r,t){if(!r||r.destroyed)return!1;let e=Buffer.from(JSON.stringify(t)),n=Buffer.alloc(5);return n[0]=m,n.writeUInt32LE(e.length,1),r.write(n),r.write(e),!0}var u=class{constructor(t,e){this.onBinary=t,this.onJSON=e,this.buf=Buffer.alloc(0)}feed(t){for(this.buf=this.buf.length?Buffer.concat([this.buf,t]):t;this.buf.length>=5;){let e=this.buf[0],n=this.buf.readUInt32LE(1);if(this.buf.length<5+n)break;let s=this.buf.slice(5,5+n);if(this.buf=this.buf.slice(5+n),e===d)this.onBinary(s);else if(e===m)try{this.onJSON(JSON.parse(s.toString()))}catch(i){console.error(`[ipc] bad JSON: ${i.message}`)}}}};function I(r,t){try{a.unlinkSync(r)}catch{}let e=l.createServer(t);return e.listen(r,()=>{try{a.chmodSync(r,511)}catch{}}),e}function N(r,{onConnect:t,onClose:e,onError:n,retryMs:s=500}={}){let i=null,o=!1,c=null;function f(){o||(i=l.createConnection(r),i.on("connect",()=>{t&&t(i)}),i.on("close",()=>{i=null,e&&e(),o||(c=setTimeout(f,s))}),i.on("error",p=>{n&&n(p)}))}return f(),{getSocket(){return i},close(){o=!0,c&&clearTimeout(c),i&&i.destroy()}}}function T(r,t,e,n){let s=Buffer.alloc(17);return s.write(r,0,8,"ascii"),s[8]=t,s.writeBigUInt64BE(BigInt(e),9),Buffer.concat([s,n])}function b(r){return r.length<17?null:{sessionId:r.slice(0,8).toString(),direction:r[8],offset:Number(r.readBigUInt64BE(9)),payload:r.slice(17)}}export{u as FrameParser,y as MAX_RING,h as RingBuffer,S as SOCK_PATH,N as connectIPC,I as createIPCServer,b as decodeDataFrame,T as encodeDataFrame,x as sendBinary,w as sendJSON};
1
+ import l from"net";import a from"fs";var g=process.env.SESSION_PROXY_SOCK||"/tmp/session-proxy.sock",y=2*1024*1024,h=class{constructor(t=y){this.capacity=t,this.chunks=[],this.totalBytes=0,this.trimmedTo=0}append(t){let e=this.totalBytes;return this.chunks.push({offset:e,data:Buffer.from(t)}),this.totalBytes+=t.length,this._trim(),e}sliceFrom(t){if(t>=this.totalBytes)return Buffer.alloc(0);if(t<this.trimmedTo)throw new Error(`RING_OVERFLOW: requested offset ${t} already trimmed (trimmedTo=${this.trimmedTo})`);let e=[];for(let n of this.chunks){if(n.offset+n.data.length<=t)continue;let i=Math.max(0,t-n.offset);e.push(n.data.slice(i))}return Buffer.concat(e)}_trim(){for(;this.chunks.length>1&&!(this.totalBytes-this.chunks[0].offset<=this.capacity);){let e=this.chunks.shift();this.trimmedTo=e.offset+e.data.length}}},d=1,p=2;function x(r,t){if(!r||r.destroyed)return!1;let e=Buffer.alloc(5);return e[0]=d,e.writeUInt32LE(t.length,1),r.write(e),r.write(t),!0}function I(r,t){if(!r||r.destroyed)return!1;let e=Buffer.from(JSON.stringify(t)),n=Buffer.alloc(5);return n[0]=p,n.writeUInt32LE(e.length,1),r.write(n),r.write(e),!0}var u=class{constructor(t,e){this.onBinary=t,this.onJSON=e,this.buf=Buffer.alloc(0)}feed(t){for(this.buf=this.buf.length?Buffer.concat([this.buf,t]):t;this.buf.length>=5;){let e=this.buf[0],n=this.buf.readUInt32LE(1);if(this.buf.length<5+n)break;let s=this.buf.slice(5,5+n);if(this.buf=this.buf.slice(5+n),e===d)this.onBinary(s);else if(e===p)try{this.onJSON(JSON.parse(s.toString()))}catch(i){console.error(`[ipc] bad JSON: ${i.message}`)}}}};function w(r,t){try{a.unlinkSync(r)}catch{}let e=l.createServer(t);return e.listen(r,()=>{try{a.chmodSync(r,511)}catch{}}),e}function N(r,{onConnect:t,onClose:e,onError:n,retryMs:s=500}={}){let i=null,o=!1,c=null;function f(){o||(i=l.createConnection(r),i.on("connect",()=>{t&&t(i)}),i.on("close",()=>{i=null,e&&e(),o||(c=setTimeout(f,s))}),i.on("error",m=>{n&&n(m)}))}return f(),{getSocket(){return i},close(){o=!0,c&&clearTimeout(c),i&&i.destroy()}}}function E(r,t,e,n){let s=Buffer.alloc(17);return s.write(r,0,8,"ascii"),s[8]=t,s.writeBigUInt64BE(BigInt(e),9),Buffer.concat([s,n])}function T(r){return r.length<17?null:{sessionId:r.slice(0,8).toString(),direction:r[8],offset:Number(r.readBigUInt64BE(9)),payload:r.slice(17)}}export{u as FrameParser,y as MAX_RING,h as RingBuffer,g as SOCK_PATH,N as connectIPC,w as createIPCServer,T as decodeDataFrame,E as encodeDataFrame,x as sendBinary,I as sendJSON};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typhons/sandbox-tools",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sandbox-relay-client": "./relay-client.js",
package/relay-client.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import b from"ws";import d from"fs";import{spawn as P,execSync as N,execFileSync as F}from"child_process";import{dirname as L}from"path";import{fileURLToPath as J}from"url";import U from"net";var $="/tmp/session-proxy.sock",Q=2*1024*1024;var B=1,I=2;function A(e,n){if(!e||e.destroyed)return!1;let t=Buffer.alloc(5);return t[0]=B,t.writeUInt32LE(n.length,1),e.write(t),e.write(n),!0}function O(e,n){if(!e||e.destroyed)return!1;let t=Buffer.from(JSON.stringify(n)),c=Buffer.alloc(5);return c[0]=I,c.writeUInt32LE(t.length,1),e.write(c),e.write(t),!0}var E=class{constructor(n,t){this.onBinary=n,this.onJSON=t,this.buf=Buffer.alloc(0)}feed(n){for(this.buf=this.buf.length?Buffer.concat([this.buf,n]):n;this.buf.length>=5;){let t=this.buf[0],c=this.buf.readUInt32LE(1);if(this.buf.length<5+c)break;let i=this.buf.slice(5,5+c);if(this.buf=this.buf.slice(5+c),t===B)this.onBinary(i);else if(t===I)try{this.onJSON(JSON.parse(i.toString()))}catch(o){console.error(`[ipc] bad JSON: ${o.message}`)}}}};function w(e,{onConnect:n,onClose:t,onError:c,retryMs:i=500}={}){let o=null,h=!1,a=null;function u(){h||(o=U.createConnection(e),o.on("connect",()=>{n&&n(o)}),o.on("close",()=>{o=null,t&&t(),h||(a=setTimeout(u,i))}),o.on("error",T=>{c&&c(T)}))}return u(),{getSocket(){return o},close(){h=!0,a&&clearTimeout(a),o&&o.destroy()}}}var ae=L(J(import.meta.url));process.on("uncaughtException",e=>{console.error(`[relay-client] UNCAUGHT: ${e.message}`),console.error(e.stack)});process.on("unhandledRejection",e=>{console.error(`[relay-client] UNHANDLED REJECTION: ${e.message||e}`)});var H=process.env.RELAY_HOST||"ssh.typhons.dev",Y="/run/e2b/.RELAY_TOKEN",K=500,G=3e3,v=15e3,M=6e4,C="@typhons/sandbox-tools";function V(){try{return d.readFileSync(Y,"utf8").trim()}catch{}return""}function R(){try{return d.readFileSync("/run/e2b/.E2B_SANDBOX_ID","utf8").trim()}catch{}return"unknown"}function r(e){console.log(`[relay-client ${new Date().toISOString().slice(11,19)}] ${e}`)}var y="unknown";function W(){if(process.env.RELAY_URL)return process.env.RELAY_URL;y=R();try{let e=d.readFileSync("/run/e2b/.SAVED_SANDBOX_ID","utf8").trim();if(e&&e!==y){r(`Clone detected (${e} -> ${y}), interrupting agents...`);try{N("/opt/sandbox/bin/stop-agents",{timeout:1e4,stdio:"inherit"})}catch{}try{d.writeFileSync("/run/e2b/.SAVED_SANDBOX_ID",y)}catch{}}}catch{}return`wss://${H}/tunnel/${y}`}var p=null,x=null;function X(){return w($,{onConnect(n){r("Connected to session-proxy via IPC"),p=n,x=new E(t=>{s&&s.readyState===b.OPEN&&s.send(t)},t=>{s&&s.readyState===b.OPEN&&s.send(JSON.stringify(t))}),n.on("data",t=>x.feed(t))},onClose(){r("IPC connection to session-proxy lost, will retry..."),p=null,x=null},onError(n){n.code!=="ENOENT"&&n.code!=="ECONNREFUSED"&&r(`IPC error: ${n.message}`)}})}var s=null,_=null,l=0,S=null;function z(){if(s){try{s.terminate()}catch{}s=null}S&&(clearTimeout(S),S=null),S=setTimeout(()=>{S=null,k()},K)}function k(){let e=++l,n=W();r(`Connecting to ${n}... (gen=${e}, sandbox=${y})`);let t={},c=V();if(c&&(t.Authorization=`Bearer ${c}`),s){try{s.terminate()}catch{}s=null}let i=new b(n,{headers:t});s=i;let o=null,h=!0,a=setTimeout(()=>{if(e===l){r(`Handshake timeout after ${v}ms \u2014 aborting and reconnecting`);try{i.terminate()}catch{}u()}},v);function u(){a&&(clearTimeout(a),a=null),o&&(clearInterval(o),o=null),e===l&&z()}function T(){h=!0,o=setInterval(()=>{if(e!==l){clearInterval(o);return}if(!h){r("No pong received \u2014 connection dead, reconnecting"),u();return}h=!1;try{i.ping()}catch{u()}},G)}i.on("open",()=>{if(e!==l){i.terminate();return}a&&(clearTimeout(a),a=null),r(`Connected${_?` (reconnect, port ${_})`:""} (gen=${e})`),T()}),i.on("pong",()=>{e===l&&(h=!0)}),i.on("message",(f,m)=>{if(e===l){if(!m){try{j(JSON.parse(f.toString()))}catch(g){r(`Bad control msg: ${g.message}`)}return}p&&!p.destroyed&&A(p,Buffer.from(f))}}),i.on("close",(f,m)=>{if(e!==l)return;let g=m?m.toString():"";f===4100&&(r(`Replaced by newer connection (code 4100, reason=${g}) \u2014 exiting`),process.exit(42)),r(`Data WS closed: code=${f}, reason=${g}`),u()}),i.on("error",f=>{e===l&&(r(`Data WS error: ${f.message} (code=${f.code||"none"})`),u())}),i.on("unexpected-response",(f,m)=>{e===l&&(r(`Data WS unexpected response: status=${m.statusCode}`),u())})}function j(e){if(e.type==="port"){_=e.port,r(`Assigned port ${e.port}`);return}if(e.type==="name"){if(r(`Name assigned: ${e.name}`),e.name&&/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(e.name)){try{F("hostname",[e.name],{timeout:5e3,stdio:"ignore"}),d.writeFileSync("/etc/hostname",e.name+`
3
- `)}catch{}try{d.writeFileSync("/run/e2b/.DEVBOX_NAME",e.name)}catch{}}return}p&&!p.destroyed&&O(p,e)}function D(){try{let e=N(`npm view ${C} version 2>/dev/null`,{timeout:15e3}).toString().trim();if(!e)return;let n="";try{n=JSON.parse(d.readFileSync("/opt/sandbox/lib/node_modules/@typhons/sandbox-tools/package.json","utf8")).version||""}catch{return}if(e!==n){r(`Update available: ${n} \u2192 ${e}, installing...`),N(`npm install -g --prefix /opt/sandbox ${C}@latest`,{timeout:12e4,stdio:"inherit"});try{N('pkill -f "check-claude-active.*--daemon" 2>/dev/null',{shell:!0,timeout:5e3})}catch{}try{let t=d.openSync("/tmp/claude-keepalive.log","a");P("/opt/sandbox/bin/check-claude-active",["--daemon"],{detached:!0,stdio:["ignore",t,t]}).unref(),d.closeSync(t)}catch{}r("Update installed, exiting for restart..."),process.exit(0)}}catch{}}var le=X();k();setTimeout(D,1e4);setInterval(D,M);r(`Relay client started (sandbox=${R()})`);
2
+ import $ from"ws";import h from"fs";import{spawn as K,execSync as g,execFileSync as X}from"child_process";import{dirname as G}from"path";import{fileURLToPath as W}from"url";import V from"net";var O=process.env.SESSION_PROXY_SOCK||"/tmp/session-proxy.sock",fe=2*1024*1024;var x=1,L=2;function b(e,n){if(!e||e.destroyed)return!1;let t=Buffer.alloc(5);return t[0]=x,t.writeUInt32LE(n.length,1),e.write(t),e.write(n),!0}function w(e,n){if(!e||e.destroyed)return!1;let t=Buffer.from(JSON.stringify(n)),c=Buffer.alloc(5);return c[0]=L,c.writeUInt32LE(t.length,1),e.write(c),e.write(t),!0}var I=class{constructor(n,t){this.onBinary=n,this.onJSON=t,this.buf=Buffer.alloc(0)}feed(n){for(this.buf=this.buf.length?Buffer.concat([this.buf,n]):n;this.buf.length>=5;){let t=this.buf[0],c=this.buf.readUInt32LE(1);if(this.buf.length<5+c)break;let i=this.buf.slice(5,5+c);if(this.buf=this.buf.slice(5+c),t===x)this.onBinary(i);else if(t===L)try{this.onJSON(JSON.parse(i.toString()))}catch(o){console.error(`[ipc] bad JSON: ${o.message}`)}}}};function k(e,{onConnect:n,onClose:t,onError:c,retryMs:i=500}={}){let o=null,p=!1,s=null;function u(){p||(o=V.createConnection(e),o.on("connect",()=>{n&&n(o)}),o.on("close",()=>{o=null,t&&t(),p||(s=setTimeout(u,i))}),o.on("error",T=>{c&&c(T)}))}return u(),{getSocket(){return o},close(){p=!0,s&&clearTimeout(s),o&&o.destroy()}}}var ge=G(W(import.meta.url));process.on("uncaughtException",e=>{console.error(`[relay-client] UNCAUGHT: ${e.message}`),console.error(e.stack)});process.on("unhandledRejection",e=>{console.error(`[relay-client] UNHANDLED REJECTION: ${e.message||e}`)});var z=process.env.RELAY_HOST||"ssh.typhons.dev",j=process.env.RELAY_SCHEME||"wss",N=process.env.E2B_RUN_DIR||"/run/e2b",q=`${N}/.RELAY_TOKEN`,Q=`${N}/.E2B_SANDBOX_ID`,U=`${N}/.SAVED_SANDBOX_ID`,Z=`${N}/.DEVBOX_NAME`,ee=parseInt(process.env.RELAY_CLIENT_RECONNECT_DELAY_MS||"1500",10),te=3e3,P=15e3,ne=6e4,Y=parseInt(process.env.RELAY_CLIENT_EVICTION_RETRY_MS||"15000",10),M=parseInt(process.env.RELAY_CLIENT_EVICTION_GIVEUP_MS||String(30*60*1e3),10),re=parseInt(process.env.RELAY_CLIENT_ID_RECHECK_MS||"10000",10),H="@typhons/sandbox-tools",oe=process.env.RELAY_CLIENT_DISABLE_UPDATE==="1";function ie(){try{return h.readFileSync(q,"utf8").trim()}catch{}return""}function B(){try{return h.readFileSync(Q,"utf8").trim()}catch{}return"unknown"}function r(e){console.log(`[relay-client ${new Date().toISOString().slice(11,19)}] ${e}`)}var d="unknown";function ce(){if(process.env.RELAY_URL)return process.env.RELAY_URL;d=B();try{let e=h.readFileSync(U,"utf8").trim();if(e&&e!==d){r(`Clone detected (${e} -> ${d}), interrupting agents...`);try{g("/opt/sandbox/bin/stop-agents",{timeout:1e4,stdio:"inherit"})}catch{}try{h.writeFileSync(U,d)}catch{}}}catch{}return`${j}://${z}/tunnel/${d}`}var E=null,R=null;function se(){return k(O,{onConnect(n){r("Connected to session-proxy via IPC"),E=n,R=new I(t=>{a&&a.readyState===$.OPEN&&a.send(t)},t=>{a&&a.readyState===$.OPEN&&a.send(JSON.stringify(t))}),n.on("data",t=>R.feed(t))},onClose(){r("IPC connection to session-proxy lost, will retry..."),E=null,R=null},onError(n){n.code!=="ENOENT"&&n.code!=="ECONNREFUSED"&&r(`IPC error: ${n.message}`)}})}var a=null,C=null,l=0,_=null,y=null;function v(e=ee){if(a){try{a.terminate()}catch{}a=null}_&&(clearTimeout(_),_=null),_=setTimeout(()=>{_=null,J()},e)}function J(){let e=++l,n=ce();r(`Connecting to ${n}... (gen=${e}, sandbox=${d})`);let t={},c=ie();if(c&&(t.Authorization=`Bearer ${c}`),a){try{a.terminate()}catch{}a=null}let i=new $(n,{headers:t});a=i;let o=null,p=!0,s=setTimeout(()=>{if(e===l){r(`Handshake timeout after ${P}ms \u2014 aborting and reconnecting`);try{i.terminate()}catch{}u()}},P);function u(){s&&(clearTimeout(s),s=null),o&&(clearInterval(o),o=null),e===l&&v()}function T(){p=!0,o=setInterval(()=>{if(e!==l){clearInterval(o);return}if(!p){r("No pong received \u2014 connection dead, reconnecting"),u();return}p=!1;try{i.ping()}catch{u()}},te)}i.on("open",()=>{if(e!==l){i.terminate();return}s&&(clearTimeout(s),s=null),r(`Connected${C?` (reconnect, port ${C})`:""} (gen=${e})`),T()}),i.on("pong",()=>{e===l&&(p=!0,y!==null&&(r(`Reclaimed slot after eviction (was evicted for ${Math.round((Date.now()-y)/1e3)}s)`),y=null))}),i.on("message",(f,m)=>{if(e===l){if(!m){try{ae(JSON.parse(f.toString()))}catch(S){r(`Bad control msg: ${S.message}`)}return}E&&!E.destroyed&&b(E,Buffer.from(f))}}),i.on("close",(f,m)=>{if(e!==l)return;let S=m?m.toString():"";if(f===4100){let D=Date.now();y===null&&(y=D);let A=D-y;A>=M&&(r(`Replaced by newer connection (code 4100) \u2014 gave up after ${Math.round(A/1e3)}s, exiting`),process.exit(42)),r(`Replaced by newer connection (code 4100, reason=${S}) \u2014 retrying in ${Y}ms (${Math.round(A/1e3)}s evicted, giveup at ${Math.round(M/1e3)}s)`),s&&(clearTimeout(s),s=null),o&&(clearInterval(o),o=null),e===l&&v(Y);return}r(`Data WS closed: code=${f}, reason=${S}`),u()}),i.on("error",f=>{e===l&&(r(`Data WS error: ${f.message} (code=${f.code||"none"})`),u())}),i.on("unexpected-response",(f,m)=>{e===l&&(r(`Data WS unexpected response: status=${m.statusCode}`),u())})}function ae(e){if(e.type==="port"){C=e.port,r(`Assigned port ${e.port}`);return}if(e.type==="name"){if(r(`Name assigned: ${e.name}`),e.name&&/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(e.name)){try{X("hostname",[e.name],{timeout:5e3,stdio:"ignore"}),h.writeFileSync("/etc/hostname",e.name+`
3
+ `)}catch{}try{h.writeFileSync(Z,e.name)}catch{}}return}E&&!E.destroyed&&w(E,e)}function F(){try{let e=g(`npm view ${H} version 2>/dev/null`,{timeout:15e3}).toString().trim();if(!e)return;let n="";try{n=JSON.parse(h.readFileSync("/opt/sandbox/lib/node_modules/@typhons/sandbox-tools/package.json","utf8")).version||""}catch{return}if(e!==n){r(`Update available: ${n} \u2192 ${e}, installing...`),g(`npm install -g --prefix /opt/sandbox ${H}@latest`,{timeout:12e4,stdio:"inherit"});try{g('pkill -f "check-claude-active.*--daemon" 2>/dev/null',{shell:!0,timeout:5e3})}catch{}try{let t=h.openSync("/tmp/claude-keepalive.log","a");K("/opt/sandbox/bin/check-claude-active",["--daemon"],{detached:!0,stdio:["ignore",t,t]}).unref(),h.closeSync(t)}catch{}r("Update installed, exiting for restart..."),process.exit(0)}}catch{}}function le(){if(process.env.RELAY_URL)return;let e=B();!e||e==="unknown"||e!==d&&(r(`Sandbox ID on disk changed (${d} -> ${e}), forcing reconnect`),d=e,v())}var Ne=se();J();setInterval(le,re);oe||(setTimeout(F,1e4),setInterval(F,ne));r(`Relay client started (sandbox=${B()})`);
package/session-proxy.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import K from"net";import j from"http";import{WebSocketServer as G}from"ws";import z from"fs";import{execFileSync as I}from"child_process";import Y from"net";import C from"fs";var x="/tmp/session-proxy.sock",q=2*1024*1024,y=class{constructor(t=q){this.capacity=t,this.chunks=[],this.totalBytes=0,this.trimmedTo=0}append(t){let s=this.totalBytes;return this.chunks.push({offset:s,data:Buffer.from(t)}),this.totalBytes+=t.length,this._trim(),s}sliceFrom(t){if(t>=this.totalBytes)return Buffer.alloc(0);if(t<this.trimmedTo)throw new Error(`RING_OVERFLOW: requested offset ${t} already trimmed (trimmedTo=${this.trimmedTo})`);let s=[];for(let r of this.chunks){if(r.offset+r.data.length<=t)continue;let o=Math.max(0,t-r.offset);s.push(r.data.slice(o))}return Buffer.concat(s)}_trim(){for(;this.chunks.length>1&&!(this.totalBytes-this.chunks[0].offset<=this.capacity);){let s=this.chunks.shift();this.trimmedTo=s.offset+s.data.length}}},_=1,R=2;function D(e,t){if(!e||e.destroyed)return!1;let s=Buffer.alloc(5);return s[0]=_,s.writeUInt32LE(t.length,1),e.write(s),e.write(t),!0}function B(e,t){if(!e||e.destroyed)return!1;let s=Buffer.from(JSON.stringify(t)),r=Buffer.alloc(5);return r[0]=R,r.writeUInt32LE(s.length,1),e.write(r),e.write(s),!0}var w=class{constructor(t,s){this.onBinary=t,this.onJSON=s,this.buf=Buffer.alloc(0)}feed(t){for(this.buf=this.buf.length?Buffer.concat([this.buf,t]):t;this.buf.length>=5;){let s=this.buf[0],r=this.buf.readUInt32LE(1);if(this.buf.length<5+r)break;let n=this.buf.slice(5,5+r);if(this.buf=this.buf.slice(5+r),s===_)this.onBinary(n);else if(s===R)try{this.onJSON(JSON.parse(n.toString()))}catch(o){console.error(`[ipc] bad JSON: ${o.message}`)}}}};function H(e,t){try{C.unlinkSync(e)}catch{}let s=Y.createServer(t);return s.listen(e,()=>{try{C.chmodSync(e,511)}catch{}}),s}function F(e,t,s,r){let n=Buffer.alloc(17);return n.write(e,0,8,"ascii"),n[8]=t,n.writeBigUInt64BE(BigInt(s),9),Buffer.concat([n,r])}function L(e){return e.length<17?null:{sessionId:e.slice(0,8).toString(),direction:e[8],offset:Number(e.readBigUInt64BE(9)),payload:e.slice(17)}}var v;try{v=(await import("@homebridge/node-pty-prebuilt-multiarch")).default}catch{try{v=(await import("node-pty")).default}catch{}}var A=parseInt(process.env.SSH_PORT||"22"),U=parseInt(process.env.WS_PORT||"49999");function i(e){console.log(`[session-proxy ${new Date().toISOString().slice(11,19)}] ${e}`)}process.on("uncaughtException",e=>{console.error(`[session-proxy] UNCAUGHT: ${e.message}`),console.error(e.stack)});process.on("unhandledRejection",e=>{console.error(`[session-proxy] UNHANDLED REJECTION: ${e.message||e}`)});var a=new Map,c=null;function P(e,t,s,r){if(!c||c.destroyed)return!1;let n=F(e,t,s,r);return D(c,n)}function d(e){!c||c.destroyed||B(c,e)}function h(e,t){let s=a.get(e);if(s){if(i(`session ${e}: closing \u2014 ${t}`),s.socket&&!s.socket.destroyed&&s.socket.destroy(),s.ptyProcess){s.closed=!0;try{s.ptyProcess.kill()}catch{}}d({type:"close",session:e}),a.delete(e)}}function X(e,t,s){let r=a.get(e);if(!r||!(r.ptyProcess||r.socket)||r.socket&&r.socket.destroyed||r.closed)return;let o=r.user2sshd.received;if(t+s.length<=o)return;let u=s;if(t<o&&(u=s.slice(o-t)),t>o){h(e,`user2sshd stream gap: expected offset ${o}, got ${t}`);return}r.ptyProcess?r.ptyProcess.write(u):r.socket.write(u),r.user2sshd.received=Math.max(o,t+s.length),d({type:"ack",session:e,direction:"user2sshd",offset:r.user2sshd.received})}function J(e,t){let s=a.get(e);if(!s)return;let r;try{r=s.sshd2user.ring.sliceFrom(t)}catch(n){h(e,`ring buffer overflow on resend: ${n.message}`);return}r.length>0?(i(`session ${e}: re-sending ${r.length}b sshd\u2192user from offset ${t}`),P(e,0,t,r)):i(`session ${e}: relay has all ${t}b sshd\u2192user, nothing to re-send`)}function V(e){if(e.type==="open"){e.mode==="pty"?v?Q(e.session,e.sshd2userOffset||0,e.user2sshdOffset||0,e):(i(`session ${e.session}: PTY mode requested but node-pty not available \u2014 rejecting`),d({type:"close",session:e.session})):Z(e.session,e.sshd2userOffset||0,e.user2sshdOffset||0);return}if(e.type==="resize"){let t=a.get(e.session);if(t?.ptyProcess)try{t.ptyProcess.resize(e.cols,e.rows)}catch{}return}if(e.type==="close"){h(e.session,"relay requested close");return}if(e.type==="ack"){let t=a.get(e.session);if(!t)return;e.direction==="sshd2user"&&typeof e.offset=="number"&&(t.sshd2user.acked=Math.max(t.sshd2user.acked,e.offset));return}if(e.type==="get-authorized-keys"){let t=e.user||"root";if(t!=="root"&&!/^[a-z_][a-z0-9_-]{0,31}$/.test(t)){d({type:"authorized-keys",requestId:e.requestId,keys:null});return}let s=t==="root"?"/root":`/home/${t}`,r=null;try{r=z.readFileSync(`${s}/.ssh/authorized_keys`,"utf8")}catch{}d({type:"authorized-keys",requestId:e.requestId,keys:r});return}}function Q(e,t,s,r){let n=a.get(e);if(n&&n.ptyProcess&&!n.closed){i(`session ${e}: re-attaching PTY (relay sshd2user=${t}, user2sshd=${s}, our sshd2user=${n.sshd2user.ring.totalBytes}, user2sshd=${n.user2sshd.received})`),n.sshd2user.acked=t,d({type:"ready",session:e,sshd2userOffset:n.sshd2user.ring.totalBytes,user2sshdOffset:n.user2sshd.received}),J(e,t);return}let o=r.user||"root",u=r.shell||null,$=process.env.SHELL||"/bin/bash",T=r.cols||80,b=r.rows||24,O=r.term||"xterm-256color",m,k,S;try{let l=I("getent",["passwd",o],{timeout:2e3}).toString().trim().split(":");m=parseInt(l[2]),k=parseInt(l[3]),S=l[5]||(o==="root"?"/root":`/home/${o}`)}catch{m=o==="root"?0:void 0,k=void 0,S=o==="root"?"/root":`/home/${o}`}let E=$,N=u?["-c",u]:["-l"];i(`session ${e}: spawning PTY ${E} ${N.join(" ")} as ${o} (uid=${m}, ${T}x${b})`);let g={socket:null,ptyProcess:null,closed:!1,sshd2user:{ring:new y,acked:0},user2sshd:{received:0}};a.set(e,g);try{let f=v.spawn(E,N,{name:O,cols:T,rows:b,cwd:S,env:{...process.env,TERM:O,HOME:S,USER:o,LOGNAME:o,SHELL:$,PATH:process.env.PATH},uid:m,gid:k});g.ptyProcess=f,f.onData(l=>{let p=Buffer.from(l),M=g.sshd2user.ring.append(p);P(e,0,M,p)}),f.onExit(({exitCode:l,signal:p})=>{i(`session ${e}: PTY exited (code=${l}, signal=${p})`),g.closed=!0,d({type:"exit",session:e,code:l,signal:p||void 0}),h(e,"pty exited")}),d({type:"ready",session:e,sshd2userOffset:0,user2sshdOffset:0})}catch(f){i(`session ${e}: PTY spawn failed: ${f.message}`),h(e,`pty spawn error: ${f.message}`)}}function Z(e,t,s){let r=a.get(e);if(r&&r.socket&&!r.socket.destroyed){i(`session ${e}: re-attaching (relay sshd2user=${t}, user2sshd=${s}, our sshd2user=${r.sshd2user.ring.totalBytes}, user2sshd=${r.user2sshd.received})`),r.sshd2user.acked=t,d({type:"ready",session:e,sshd2userOffset:r.sshd2user.ring.totalBytes,user2sshdOffset:r.user2sshd.received}),J(e,t);return}i(`session ${e}: connecting to sshd :${A}`);let n={socket:null,sshd2user:{ring:new y,acked:0},user2sshd:{received:0}};a.set(e,n);let o=K.createConnection({host:"127.0.0.1",port:A},()=>{i(`session ${e}: sshd connected`),o.setNoDelay(!0),n.socket=o,d({type:"ready",session:e,sshd2userOffset:0,user2sshdOffset:0})});n.socket=o,o.on("data",u=>{let $=n.sshd2user.ring.append(u);P(e,0,$,u)}),o.on("close",()=>h(e,"sshd closed")),o.on("error",u=>h(e,`sshd error: ${u.message}`))}var le=H(x,e=>{if(i("Relay-client connected via IPC"),c&&!c.destroyed&&(i("Replacing previous relay-client IPC connection"),c.destroy()),c=e,a.size>0){let s=[];for(let[r,n]of a)s.push({id:r,sshd2userOffset:n.sshd2user.ring.totalBytes,user2sshdOffset:n.user2sshd.received,mode:n.ptyProcess?"pty":"sshd"});B(e,{type:"existing-sessions",sessions:s})}let t=new w(s=>{let r=L(s);r&&r.direction===1&&X(r.sessionId,r.offset,r.payload)},s=>{V(s)});e.on("data",s=>t.feed(s)),e.on("close",()=>{i("Relay-client IPC disconnected"),c===e&&(c=null)}),e.on("error",s=>{i(`IPC error: ${s.message}`)})});i(`IPC server listening on ${x}`);function ee(){try{return z.readFileSync("/run/e2b/.E2B_SANDBOX_ID","utf8").trim()}catch{}return"unknown"}var W=j.createServer((e,t)=>{t.setHeader("Content-Type","application/json"),e.url==="/health"?t.end(JSON.stringify({ok:!0,sandboxId:ee(),sessions:a.size,ipcConnected:!!(c&&!c.destroyed)})):(t.writeHead(404),t.end('{"error":"not found"}'))}),te=new G({server:W});te.on("connection",e=>{i("Keepalive WS connected from relay"),e.on("close",(t,s)=>i(`Keepalive WS closed: code=${t}, reason=${s?s.toString():""}`)),e.on("error",t=>i(`Keepalive WS error: ${t.message}`))});W.listen(U,"0.0.0.0",()=>{i(`Keepalive WS server on :${U}`)});
2
+ import K from"net";import j from"http";import{WebSocketServer as G}from"ws";import z from"fs";import{execFileSync as I}from"child_process";import M from"net";import _ from"fs";var x=process.env.SESSION_PROXY_SOCK||"/tmp/session-proxy.sock",q=2*1024*1024,y=class{constructor(t=q){this.capacity=t,this.chunks=[],this.totalBytes=0,this.trimmedTo=0}append(t){let s=this.totalBytes;return this.chunks.push({offset:s,data:Buffer.from(t)}),this.totalBytes+=t.length,this._trim(),s}sliceFrom(t){if(t>=this.totalBytes)return Buffer.alloc(0);if(t<this.trimmedTo)throw new Error(`RING_OVERFLOW: requested offset ${t} already trimmed (trimmedTo=${this.trimmedTo})`);let s=[];for(let r of this.chunks){if(r.offset+r.data.length<=t)continue;let o=Math.max(0,t-r.offset);s.push(r.data.slice(o))}return Buffer.concat(s)}_trim(){for(;this.chunks.length>1&&!(this.totalBytes-this.chunks[0].offset<=this.capacity);){let s=this.chunks.shift();this.trimmedTo=s.offset+s.data.length}}},C=1,R=2;function D(e,t){if(!e||e.destroyed)return!1;let s=Buffer.alloc(5);return s[0]=C,s.writeUInt32LE(t.length,1),e.write(s),e.write(t),!0}function B(e,t){if(!e||e.destroyed)return!1;let s=Buffer.from(JSON.stringify(t)),r=Buffer.alloc(5);return r[0]=R,r.writeUInt32LE(s.length,1),e.write(r),e.write(s),!0}var v=class{constructor(t,s){this.onBinary=t,this.onJSON=s,this.buf=Buffer.alloc(0)}feed(t){for(this.buf=this.buf.length?Buffer.concat([this.buf,t]):t;this.buf.length>=5;){let s=this.buf[0],r=this.buf.readUInt32LE(1);if(this.buf.length<5+r)break;let n=this.buf.slice(5,5+r);if(this.buf=this.buf.slice(5+r),s===C)this.onBinary(n);else if(s===R)try{this.onJSON(JSON.parse(n.toString()))}catch(o){console.error(`[ipc] bad JSON: ${o.message}`)}}}};function H(e,t){try{_.unlinkSync(e)}catch{}let s=M.createServer(t);return s.listen(e,()=>{try{_.chmodSync(e,511)}catch{}}),s}function F(e,t,s,r){let n=Buffer.alloc(17);return n.write(e,0,8,"ascii"),n[8]=t,n.writeBigUInt64BE(BigInt(s),9),Buffer.concat([n,r])}function L(e){return e.length<17?null:{sessionId:e.slice(0,8).toString(),direction:e[8],offset:Number(e.readBigUInt64BE(9)),payload:e.slice(17)}}var w;try{w=(await import("@homebridge/node-pty-prebuilt-multiarch")).default}catch{try{w=(await import("node-pty")).default}catch{}}var A=parseInt(process.env.SSH_PORT||"22"),U=parseInt(process.env.WS_PORT||"49999");function i(e){console.log(`[session-proxy ${new Date().toISOString().slice(11,19)}] ${e}`)}process.on("uncaughtException",e=>{console.error(`[session-proxy] UNCAUGHT: ${e.message}`),console.error(e.stack)});process.on("unhandledRejection",e=>{console.error(`[session-proxy] UNHANDLED REJECTION: ${e.message||e}`)});var a=new Map,c=null;function P(e,t,s,r){if(!c||c.destroyed)return!1;let n=F(e,t,s,r);return D(c,n)}function d(e){!c||c.destroyed||B(c,e)}function h(e,t){let s=a.get(e);if(s){if(i(`session ${e}: closing \u2014 ${t}`),s.socket&&!s.socket.destroyed&&s.socket.destroy(),s.ptyProcess){s.closed=!0;try{s.ptyProcess.kill()}catch{}}d({type:"close",session:e}),a.delete(e)}}function X(e,t,s){let r=a.get(e);if(!r||!(r.ptyProcess||r.socket)||r.socket&&r.socket.destroyed||r.closed)return;let o=r.user2sshd.received;if(t+s.length<=o)return;let u=s;if(t<o&&(u=s.slice(o-t)),t>o){h(e,`user2sshd stream gap: expected offset ${o}, got ${t}`);return}r.ptyProcess?r.ptyProcess.write(u):r.socket.write(u),r.user2sshd.received=Math.max(o,t+s.length),d({type:"ack",session:e,direction:"user2sshd",offset:r.user2sshd.received})}function J(e,t){let s=a.get(e);if(!s)return;let r;try{r=s.sshd2user.ring.sliceFrom(t)}catch(n){h(e,`ring buffer overflow on resend: ${n.message}`);return}r.length>0?(i(`session ${e}: re-sending ${r.length}b sshd\u2192user from offset ${t}`),P(e,0,t,r)):i(`session ${e}: relay has all ${t}b sshd\u2192user, nothing to re-send`)}function V(e){if(e.type==="open"){e.mode==="pty"?w?Q(e.session,e.sshd2userOffset||0,e.user2sshdOffset||0,e):(i(`session ${e.session}: PTY mode requested but node-pty not available \u2014 rejecting`),d({type:"close",session:e.session})):Z(e.session,e.sshd2userOffset||0,e.user2sshdOffset||0);return}if(e.type==="resize"){let t=a.get(e.session);if(t?.ptyProcess)try{t.ptyProcess.resize(e.cols,e.rows)}catch{}return}if(e.type==="close"){h(e.session,"relay requested close");return}if(e.type==="ack"){let t=a.get(e.session);if(!t)return;e.direction==="sshd2user"&&typeof e.offset=="number"&&(t.sshd2user.acked=Math.max(t.sshd2user.acked,e.offset));return}if(e.type==="get-authorized-keys"){let t=e.user||"root";if(t!=="root"&&!/^[a-z_][a-z0-9_-]{0,31}$/.test(t)){d({type:"authorized-keys",requestId:e.requestId,keys:null});return}let s=t==="root"?"/root":`/home/${t}`,r=null;try{r=z.readFileSync(`${s}/.ssh/authorized_keys`,"utf8")}catch{}d({type:"authorized-keys",requestId:e.requestId,keys:r});return}}function Q(e,t,s,r){let n=a.get(e);if(n&&n.ptyProcess&&!n.closed){i(`session ${e}: re-attaching PTY (relay sshd2user=${t}, user2sshd=${s}, our sshd2user=${n.sshd2user.ring.totalBytes}, user2sshd=${n.user2sshd.received})`),n.sshd2user.acked=t,d({type:"ready",session:e,sshd2userOffset:n.sshd2user.ring.totalBytes,user2sshdOffset:n.user2sshd.received}),J(e,t);return}let o=r.user||"root",u=r.shell||null,$=process.env.SHELL||"/bin/bash",O=r.cols||80,T=r.rows||24,b=r.term||"xterm-256color",S,k,m;try{let l=I("getent",["passwd",o],{timeout:2e3}).toString().trim().split(":");S=parseInt(l[2]),k=parseInt(l[3]),m=l[5]||(o==="root"?"/root":`/home/${o}`)}catch{S=o==="root"?0:void 0,k=void 0,m=o==="root"?"/root":`/home/${o}`}let E=$,N=u?["-c",u]:["-l"];i(`session ${e}: spawning PTY ${E} ${N.join(" ")} as ${o} (uid=${S}, ${O}x${T})`);let g={socket:null,ptyProcess:null,closed:!1,sshd2user:{ring:new y,acked:0},user2sshd:{received:0}};a.set(e,g);try{let f=w.spawn(E,N,{name:b,cols:O,rows:T,cwd:m,env:{...process.env,TERM:b,HOME:m,USER:o,LOGNAME:o,SHELL:$,PATH:process.env.PATH},uid:S,gid:k});g.ptyProcess=f,f.onData(l=>{let p=Buffer.from(l),Y=g.sshd2user.ring.append(p);P(e,0,Y,p)}),f.onExit(({exitCode:l,signal:p})=>{i(`session ${e}: PTY exited (code=${l}, signal=${p})`),g.closed=!0,d({type:"exit",session:e,code:l,signal:p||void 0}),h(e,"pty exited")}),d({type:"ready",session:e,sshd2userOffset:0,user2sshdOffset:0})}catch(f){i(`session ${e}: PTY spawn failed: ${f.message}`),h(e,`pty spawn error: ${f.message}`)}}function Z(e,t,s){let r=a.get(e);if(r&&r.socket&&!r.socket.destroyed){i(`session ${e}: re-attaching (relay sshd2user=${t}, user2sshd=${s}, our sshd2user=${r.sshd2user.ring.totalBytes}, user2sshd=${r.user2sshd.received})`),r.sshd2user.acked=t,d({type:"ready",session:e,sshd2userOffset:r.sshd2user.ring.totalBytes,user2sshdOffset:r.user2sshd.received}),J(e,t);return}i(`session ${e}: connecting to sshd :${A}`);let n={socket:null,sshd2user:{ring:new y,acked:0},user2sshd:{received:0}};a.set(e,n);let o=K.createConnection({host:"127.0.0.1",port:A},()=>{i(`session ${e}: sshd connected`),o.setNoDelay(!0),n.socket=o,d({type:"ready",session:e,sshd2userOffset:0,user2sshdOffset:0})});n.socket=o,o.on("data",u=>{let $=n.sshd2user.ring.append(u);P(e,0,$,u)}),o.on("close",()=>h(e,"sshd closed")),o.on("error",u=>h(e,`sshd error: ${u.message}`))}var le=H(x,e=>{if(i("Relay-client connected via IPC"),c&&!c.destroyed&&(i("Replacing previous relay-client IPC connection"),c.destroy()),c=e,a.size>0){let s=[];for(let[r,n]of a)s.push({id:r,sshd2userOffset:n.sshd2user.ring.totalBytes,user2sshdOffset:n.user2sshd.received,mode:n.ptyProcess?"pty":"sshd"});B(e,{type:"existing-sessions",sessions:s})}let t=new v(s=>{let r=L(s);r&&r.direction===1&&X(r.sessionId,r.offset,r.payload)},s=>{V(s)});e.on("data",s=>t.feed(s)),e.on("close",()=>{i("Relay-client IPC disconnected"),c===e&&(c=null)}),e.on("error",s=>{i(`IPC error: ${s.message}`)})});i(`IPC server listening on ${x}`);function ee(){try{return z.readFileSync("/run/e2b/.E2B_SANDBOX_ID","utf8").trim()}catch{}return"unknown"}var W=j.createServer((e,t)=>{t.setHeader("Content-Type","application/json"),e.url==="/health"?t.end(JSON.stringify({ok:!0,sandboxId:ee(),sessions:a.size,ipcConnected:!!(c&&!c.destroyed)})):(t.writeHead(404),t.end('{"error":"not found"}'))}),te=new G({server:W});te.on("connection",e=>{i("Keepalive WS connected from relay"),e.on("close",(t,s)=>i(`Keepalive WS closed: code=${t}, reason=${s?s.toString():""}`)),e.on("error",t=>i(`Keepalive WS error: ${t.message}`))});W.listen(U,"0.0.0.0",()=>{i(`Keepalive WS server on :${U}`)});