@typhons/sandbox-tools 0.5.6 → 0.5.9

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,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{execSync as r}from"child_process";import f from"fs";import{setTimeout as x}from"timers/promises";var u=30,g=1,b=100,w=!1,v=!1,d=process.argv.slice(2);for(let e=0;e<d.length;e++)d[e]==="--json"?w=!0:d[e]==="--threshold"?u=parseInt(d[++e],10):d[e]==="--daemon"&&(v=!0);function _(e){try{if(e==="claude")return r("pgrep -x claude 2>/dev/null | head -1",{encoding:"utf8"}).trim()||null;if(e==="codex"){let t=r("pgrep -f 'node.*codex' 2>/dev/null",{encoding:"utf8"}).trim().split(`
3
- `);for(let n of t)if(n)try{let o=f.readFileSync(`/proc/${n}/cmdline`,"utf8").replace(/\0/g," ");if(/codex\.js|codex\/bin|@openai\/codex/.test(o))return n}catch{}}}catch{}return null}function T(){try{let e=r('find /home -name "*.jsonl" -path "*/.claude/projects/*" ! -path "*/subagents/*" -printf "%T@ %p\\n" 2>/dev/null | sort -rn | head -1',{encoding:"utf8"}).trim();if(e){let t=parseInt(e.split(" ")[0],10);return Math.floor(Date.now()/1e3)-t}}catch{}return-1}function E(){let e=-1,t=["/home/*/.codex/log/codex-tui.log","/root/.codex/log/codex-tui.log"];for(let n of t)try{let o=r(`ls ${n} 2>/dev/null`,{encoding:"utf8"}).trim().split(`
4
- `);for(let c of o)if(c)try{let a=f.statSync(c),l=Math.floor(Date.now()/1e3)-Math.floor(a.mtimeMs/1e3);(e===-1||l<e)&&(e=l)}catch{}}catch{}return e}function $(e){if(!e)return 0;let t=0;try{let n=r(`pgrep -P ${e} 2>/dev/null`,{encoding:"utf8"}).trim().split(`
5
- `);for(let o of n)if(o)try{if(f.readFileSync(`/proc/${o}/cmdline`,"utf8").replace(/\0/g," ").includes("check-claude-active"))continue;t++}catch{t++}}catch{}return t}function s(e){try{let n=f.readFileSync(`/proc/${e}/io`,"utf8").match(/^rchar:\s*(\d+)/m);return n?parseInt(n[1],10):0}catch{return 0}}async function A(e){if(!e)return 0;let t=s(e);return await x(g*1e3),s(e)-t}async function I(){let e=_("claude"),t=_("codex"),n=T(),o=E(),c=n>=0&&n<u||o>=0&&o<u,a=$(e),l=$(t),y=a>0||l>0,i=0;if(e&&t){let O=s(e),S=s(t);await x(g*1e3);let N=s(e),D=s(t);i=Math.max(N-O,D-S)}else e?i=await A(e):t&&(i=await A(t));let h=i>b,p=c||y||h,m=e&&t?"both":e?"claude":t?"codex":"none";return w||v?console.log(JSON.stringify({active:p,agent:m,claude_pid:e?parseInt(e):null,codex_pid:t?parseInt(t):null,claude_jsonl_age:n,codex_log_age:o,log_active:c,children:a+l,io_delta:i,io_active:h})):!e&&!t?console.log("Agents: not running"):p?(console.log(`Agent: ACTIVE (${m})`),c&&(n>=0&&n<u&&console.log(` Claude JSONL written ${n}s ago`),o>=0&&o<u&&console.log(` Codex log written ${o}s ago`)),y&&console.log(` ${a} claude + ${l} codex child process(es)`),h&&console.log(` I/O: ${i} bytes read in ${g}s`)):(console.log(`Agent: idle (${m} running, waiting for user)`),e&&console.log(` Claude JSONL last written ${n}s ago`),t&&console.log(` Codex log last written ${o}s ago`),console.log(" No child processes"),console.log(` I/O: ${i} bytes in ${g}s`)),p}function C(){try{return f.readFileSync("/run/e2b/.DEVBOX_NAME","utf8").trim()}catch{}return""}if(v){let e=process.env.E2B_HTTP_DOMAIN||"d.typhons.dev";for(console.log(`[agent-keepalive] domain=${e} interval=30s`);;){if(await I()){let n=C();if(n){let o=`https://${n}.${e}:49999/`;try{r(`curl -sf -o /dev/null -w '%{http_code}' --max-time 5 "${o}" 2>/dev/null`,{encoding:"utf8"})}catch{}}else console.log("[agent-keepalive] no devbox name available yet, skipping ping")}await x(29e3)}}else{let e=await I();process.exit(e?0:1)}
2
+ import{execSync as g}from"child_process";import f from"fs";import{setTimeout as D}from"timers/promises";import{execSync as b}from"child_process";function m(e){let t="";try{t=b(`pgrep -x ${e} 2>/dev/null`,{encoding:"utf8"})}catch{}return t.split(`
3
+ `).map(o=>o.trim()).filter(Boolean)}var a=30,x=1,T=100,M=!1,v=!1,r=process.argv.slice(2);for(let e=0;e<r.length;e++)r[e]==="--json"?M=!0:r[e]==="--threshold"?a=parseInt(r[++e],10):r[e]==="--daemon"&&(v=!0);function E(){try{let e=g('find /home -name "*.jsonl" -path "*/.claude/projects/*" ! -path "*/subagents/*" -printf "%T@ %p\\n" 2>/dev/null | sort -rn | head -1',{encoding:"utf8"}).trim();if(e){let t=parseInt(e.split(" ")[0],10);return Math.floor(Date.now()/1e3)-t}}catch{}return-1}function B(){let e=-1,t=["/home/*/.codex/log/codex-tui.log","/root/.codex/log/codex-tui.log"];for(let o of t)try{let n=g(`ls ${o} 2>/dev/null`,{encoding:"utf8"}).trim().split(`
4
+ `);for(let l of n)if(l)try{let i=f.statSync(l),s=Math.floor(Date.now()/1e3)-Math.floor(i.mtimeMs/1e3);(e===-1||s<e)&&(e=s)}catch{}}catch{}return e}function O(e){let t=0;for(let o of e)try{let n=g(`pgrep -P ${o} 2>/dev/null`,{encoding:"utf8"}).trim().split(`
5
+ `);for(let l of n)if(l)try{if(f.readFileSync(`/proc/${l}/cmdline`,"utf8").replace(/\0/g," ").includes("check-claude-active"))continue;t++}catch{t++}}catch{}return t}function w(e){try{let o=f.readFileSync(`/proc/${e}/io`,"utf8").match(/^rchar:\s*(\d+)/m);return o?parseInt(o[1],10):0}catch{return 0}}async function N(){let e=m("claude"),t=m("codex"),o=E(),n=B(),l=o>=0&&o<a||n>=0&&n<a,i=O(e),s=O(t),$=i>0||s>0,y=[...e,...t],_=y.map(w);await D(x*1e3);let A=y.map(w),I=0,S=0;for(let c=0;c<e.length;c++)I+=Math.max(0,A[c]-_[c]);for(let c=0;c<t.length;c++)S+=Math.max(0,A[e.length+c]-_[e.length+c]);let d=Math.max(I,S),u=d>T,h=l||$||u,p=e.length&&t.length?"both":e.length?"claude":t.length?"codex":"none";return M||v?console.log(JSON.stringify({active:h,agent:p,claude_pids:e.map(c=>parseInt(c,10)),codex_pids:t.map(c=>parseInt(c,10)),claude_jsonl_age:o,codex_log_age:n,log_active:l,children:i+s,io_delta:d,io_active:u})):!e.length&&!t.length?console.log("Agents: not running"):h?(console.log(`Agent: ACTIVE (${p}; ${e.length} claude + ${t.length} codex)`),l&&(o>=0&&o<a&&console.log(` Claude JSONL written ${o}s ago`),n>=0&&n<a&&console.log(` Codex log written ${n}s ago`)),$&&console.log(` ${i} claude + ${s} codex child process(es)`),u&&console.log(` I/O: ${d} bytes read in ${x}s`)):(console.log(`Agent: idle (${p}; ${e.length} claude + ${t.length} codex running, waiting for user)`),e.length&&console.log(` Claude JSONL last written ${o}s ago`),t.length&&console.log(` Codex log last written ${n}s ago`),console.log(" No child processes"),console.log(` I/O: ${d} bytes in ${x}s`)),h}function C(){try{return f.readFileSync("/run/e2b/.DEVBOX_NAME","utf8").trim()}catch{}return""}if(v){let e=process.env.E2B_HTTP_DOMAIN||"d.typhons.dev";for(console.log(`[agent-keepalive] domain=${e} interval=30s`);;){if(await N()){let o=C();if(o){let n=`https://${o}.${e}:49999/`;try{g(`curl -sf -o /dev/null -w '%{http_code}' --max-time 5 "${n}" 2>/dev/null`,{encoding:"utf8"})}catch{}}else console.log("[agent-keepalive] no devbox name available yet, skipping ping")}await D(29e3)}}else{let e=await N();process.exit(e?0:1)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typhons/sandbox-tools",
3
- "version": "0.5.6",
3
+ "version": "0.5.9",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sandbox-relay-client": "./relay-client.js",
package/relay-client.js CHANGED
@@ -1,3 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import g from"ws";import J from"http";import h from"fs";import{spawn as K,execSync as _,execFileSync as M}from"child_process";import{dirname as V}from"path";import{fileURLToPath as X}from"url";import F from"net";var O=process.env.SESSION_PROXY_SOCK||"/tmp/session-proxy.sock",ae=2*1024*1024;var B=1,x=2;function b(e,r){if(!e||e.destroyed)return!1;let t=Buffer.alloc(5);return t[0]=B,t.writeUInt32LE(r.length,1),e.write(t),e.write(r),!0}function w(e,r){if(!e||e.destroyed)return!1;let t=Buffer.from(JSON.stringify(r)),n=Buffer.alloc(5);return n[0]=x,n.writeUInt32LE(t.length,1),e.write(n),e.write(t),!0}var T=class{constructor(r,t){this.onBinary=r,this.onJSON=t,this.buf=Buffer.alloc(0)}feed(r){for(this.buf=this.buf.length?Buffer.concat([this.buf,r]):r;this.buf.length>=5;){let t=this.buf[0],n=this.buf.readUInt32LE(1);if(this.buf.length<5+n)break;let c=this.buf.slice(5,5+n);if(this.buf=this.buf.slice(5+n),t===B)this.onBinary(c);else if(t===x)try{this.onJSON(JSON.parse(c.toString()))}catch(o){console.error(`[ipc] bad JSON: ${o.message}`)}}}};function C(e,{onConnect:r,onClose:t,onError:n,retryMs:c=500}={}){let o=null,u=!1,a=null;function f(){u||(o=F.createConnection(e),o.on("connect",()=>{r&&r(o)}),o.on("close",()=>{o=null,t&&t(),u||(a=setTimeout(f,c))}),o.on("error",A=>{n&&n(A)}))}return f(),{getSocket(){return o},close(){u=!0,a&&clearTimeout(a),o&&o.destroy()}}}var _e=V(X(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 G=process.env.RELAY_HOST||"ssh.typhons.dev",j=process.env.RELAY_SCHEME||"wss",I=process.env.E2B_RUN_DIR||"/run/e2b",W=`${I}/.RELAY_TOKEN`,z=`${I}/.E2B_SANDBOX_ID`,D=`${I}/.SAVED_SANDBOX_ID`,Q=`${I}/.DEVBOX_NAME`,Z=parseInt(process.env.RELAY_CLIENT_RECONNECT_DELAY_MS||"1500",10),q=3e3,P=15e3,ee=6e4,k=parseInt(process.env.RELAY_CLIENT_VERIFY_REJECT_RETRY_MS||"10000",10),L="@typhons/sandbox-tools",te=process.env.RELAY_CLIENT_DISABLE_UPDATE==="1",N=parseInt(process.env.KEEPALIVE_PORT||"49999");function ne(){try{return h.readFileSync(W,"utf8").trim()}catch{}return""}function $(){try{return h.readFileSync(z,"utf8").trim()}catch{}return"unknown"}function i(e){console.log(`[relay-client ${new Date().toISOString().slice(11,19)}] ${e}`)}var m="unknown";function re(){if(process.env.RELAY_URL)return process.env.RELAY_URL;m=$();try{let e=h.readFileSync(D,"utf8").trim();if(e&&e!==m){i(`Clone detected (${e} -> ${m}), interrupting agents...`);try{_("/opt/sandbox/bin/stop-agents",{timeout:1e4,stdio:"inherit"})}catch{}try{h.writeFileSync(D,m)}catch{}}}catch{}return`${j}://${G}/tunnel/${m}`}var p=null,R=null;function oe(){return C(O,{onConnect(r){i("Connected to session-proxy via IPC"),p=r,R=new T(t=>{s&&s.readyState===g.OPEN&&s.send(t)},t=>{s&&s.readyState===g.OPEN&&s.send(JSON.stringify(t))}),r.on("data",t=>R.feed(t))},onClose(){i("IPC connection to session-proxy lost, will retry..."),p=null,R=null},onError(r){r.code!=="ENOENT"&&r.code!=="ECONNREFUSED"&&i(`IPC error: ${r.message}`)}})}var s=null,v=null,l=0,S=null;function H(e=Z){if(s){try{s.terminate()}catch{}s=null}S&&(clearTimeout(S),S=null),S=setTimeout(()=>{S=null,Y()},e)}function Y(){let e=++l,r=re();i(`Connecting to ${r}... (gen=${e}, sandbox=${m})`);let t={},n=ne();if(n&&(t.Authorization=`Bearer ${n}`),s){try{s.terminate()}catch{}s=null}let c=new g(r,{headers:t});s=c;let o=null,u=!0,a=setTimeout(()=>{if(e===l){i(`Handshake timeout after ${P}ms \u2014 aborting and reconnecting`);try{c.terminate()}catch{}f()}},P);function f(){a&&(clearTimeout(a),a=null),o&&(clearInterval(o),o=null),e===l&&H()}function A(){u=!0,o=setInterval(()=>{if(e!==l){clearInterval(o);return}if(!u){i("No pong received \u2014 connection dead, reconnecting"),f();return}u=!1;try{c.ping()}catch{f()}},q)}c.on("open",()=>{if(e!==l){c.terminate();return}a&&(clearTimeout(a),a=null),i(`Connected${v?` (reconnect, port ${v})`:""} (gen=${e})`),A()}),c.on("pong",()=>{e===l&&(u=!0)}),c.on("message",(d,y)=>{if(e===l){if(!y){try{ie(JSON.parse(d.toString()))}catch(E){i(`Bad control msg: ${E.message}`)}return}p&&!p.destroyed&&b(p,Buffer.from(d))}}),c.on("close",(d,y)=>{if(e!==l)return;let E=y?y.toString():"";if(d===4100&&(i(`Replaced by newer connection (code 4100, reason=${E}) \u2014 exiting`),process.exit(42)),d===4101){i(`Identity verification rejected (code 4101, reason=${E}) \u2014 retrying in ${k}ms (likely waiting for envd to update /run/e2b/.E2B_SANDBOX_ID)`),a&&(clearTimeout(a),a=null),o&&(clearInterval(o),o=null),e===l&&H(k);return}i(`Data WS closed: code=${d}, reason=${E}`),f()}),c.on("error",d=>{e===l&&(i(`Data WS error: ${d.message} (code=${d.code||"none"})`),f())}),c.on("unexpected-response",(d,y)=>{e===l&&(i(`Data WS unexpected response: status=${y.statusCode}`),f())})}function ie(e){if(e.type==="port"){v=e.port,i(`Assigned port ${e.port}`);return}if(e.type==="name"){if(i(`Name assigned: ${e.name}`),e.name&&/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(e.name)){try{M("hostname",[e.name],{timeout:5e3,stdio:"ignore"}),h.writeFileSync("/etc/hostname",e.name+`
3
- `)}catch{}try{h.writeFileSync(Q,e.name)}catch{}}return}p&&!p.destroyed&&w(p,e)}function U(){try{let e=_(`npm view ${L} version 2>/dev/null`,{timeout:15e3}).toString().trim();if(!e)return;let r="";try{r=JSON.parse(h.readFileSync("/opt/sandbox/lib/node_modules/@typhons/sandbox-tools/package.json","utf8")).version||""}catch{return}if(e!==r){i(`Update available: ${r} \u2192 ${e}, installing...`),_(`npm install -g --prefix /opt/sandbox ${L}@latest`,{timeout:12e4,stdio:"inherit"});try{_('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{}i("Update installed, exiting for restart..."),process.exit(0)}}catch{}}function ce(){let e=J.createServer((t,n)=>{if(n.setHeader("Content-Type","application/json"),t.method==="GET"&&t.url==="/health"){n.end(JSON.stringify({ok:!0,sandboxId:$(),wsConnected:!!(s&&s.readyState===g.OPEN)}));return}if(t.method==="POST"&&t.url==="/_relay/verify"){let c="";t.on("data",o=>{c+=o,c.length>4096&&t.destroy()}),t.on("end",()=>{let o;try{o=JSON.parse(c).nonce}catch{n.writeHead(400),n.end('{"error":"bad json"}');return}if(typeof o!="string"||!o.length||o.length>256){n.writeHead(400),n.end('{"error":"missing or invalid nonce"}');return}if(!s||s.readyState!==g.OPEN){n.writeHead(503),n.end('{"error":"WS not connected"}');return}try{s.send(JSON.stringify({type:"verify-ack",nonce:o}))}catch(u){n.writeHead(500),n.end(`{"error":"${u.message}"}`);return}n.writeHead(200),n.end('{"ok":true}')}),t.on("error",()=>{try{n.writeHead(400),n.end('{"error":"req error"}')}catch{}});return}n.writeHead(404),n.end('{"error":"not found"}')}),r=!1;return e.on("error",t=>{if(t.code==="EADDRINUSE"&&!r){r=!0,i(`Port ${N} held by another process \u2014 killing it (likely a pre-0.5.5 session-proxy that owned this port; watchdog will restart it under new code which no longer binds it)`);try{_("pkill -f /opt/sandbox/lib/node_modules/@typhons/sandbox-tools/session-proxy.js",{timeout:3e3})}catch{}setTimeout(()=>{try{e.listen(N,"0.0.0.0")}catch{}},1500);return}i(`Keepalive HTTP server error: ${t.message}`)}),e.listen(N,"0.0.0.0",()=>{i(`Keepalive HTTP server on :${N}`)}),e}var ge=oe();ce();Y();te||(setTimeout(U,1e4),setInterval(U,ee));i(`Relay client started (sandbox=${$()})`);
2
+ import N from"ws";import j from"http";import h from"fs";import{spawn as G,execSync as T,execFileSync as W}from"child_process";import{dirname as z}from"path";import{fileURLToPath as Q}from"url";import V from"net";var O=process.env.SESSION_PROXY_SOCK||"/tmp/session-proxy.sock",pe=2*1024*1024;var b=1,B=2;function D(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 C(e,n){if(!e||e.destroyed)return!1;let t=Buffer.from(JSON.stringify(n)),r=Buffer.alloc(5);return r[0]=B,r.writeUInt32LE(t.length,1),e.write(r),e.write(t),!0}var $=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],r=this.buf.readUInt32LE(1);if(this.buf.length<5+r)break;let c=this.buf.slice(5,5+r);if(this.buf=this.buf.slice(5+r),t===b)this.onBinary(c);else if(t===B)try{this.onJSON(JSON.parse(c.toString()))}catch(o){console.error(`[ipc] bad JSON: ${o.message}`)}}}};function k(e,{onConnect:n,onClose:t,onError:r,retryMs:c=500}={}){let o=null,u=!1,a=null;function d(){u||(o=V.createConnection(e),o.on("connect",()=>{n&&n(o)}),o.on("close",()=>{o=null,t&&t(),u||(a=setTimeout(d,c))}),o.on("error",E=>{r&&r(E)}))}return d(),{getSocket(){return o},close(){u=!0,a&&clearTimeout(a),o&&o.destroy()}}}import g from"fs";import R from"path";var L="# typhons-managed-hostname";function X(e){let n=e.replace(/\.local$/,"");return`127.0.1.1 ${n===e?e:`${e} ${n}`} ${L}`}function H(e,n="/etc/hosts"){let t=X(e),r="",c=420;try{r=g.readFileSync(n,"utf8"),c=g.statSync(n).mode&511}catch{}let o=r.split(`
3
+ `);o.length>0&&o[o.length-1]===""&&o.pop();let u=o.filter(f=>!f.includes(L));u.push(t);let a=u.join(`
4
+ `)+`
5
+ `;if(a===r)return!1;let d=R.dirname(n),E=R.basename(n),l=R.join(d,`.${E}.tmp.${process.pid}.${Date.now()}`);g.writeFileSync(l,a,{mode:c});try{g.renameSync(l,n)}catch(f){try{g.unlinkSync(l)}catch{}throw f}return!0}var we=z(Q(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",q=process.env.RELAY_SCHEME||"wss",A=process.env.E2B_RUN_DIR||"/run/e2b",ee=`${A}/.RELAY_TOKEN`,te=`${A}/.E2B_SANDBOX_ID`,P=`${A}/.SAVED_SANDBOX_ID`,ne=`${A}/.DEVBOX_NAME`,re=parseInt(process.env.RELAY_CLIENT_RECONNECT_DELAY_MS||"1500",10),oe=3e3,U=15e3,ie=6e4,F=parseInt(process.env.RELAY_CLIENT_VERIFY_REJECT_RETRY_MS||"10000",10),Y="@typhons/sandbox-tools",ce=process.env.RELAY_CLIENT_DISABLE_UPDATE==="1",I=parseInt(process.env.KEEPALIVE_PORT||"49999");function se(){try{return h.readFileSync(ee,"utf8").trim()}catch{}return""}function x(){try{return h.readFileSync(te,"utf8").trim()}catch{}return"unknown"}function i(e){console.log(`[relay-client ${new Date().toISOString().slice(11,19)}] ${e}`)}var m="unknown";function ae(){if(process.env.RELAY_URL)return process.env.RELAY_URL;m=x();try{let e=h.readFileSync(P,"utf8").trim();if(e&&e!==m){i(`Clone detected (${e} -> ${m}), interrupting agents...`);try{T("/opt/sandbox/bin/stop-agents",{timeout:1e4,stdio:"inherit"})}catch{}try{h.writeFileSync(P,m)}catch{}}}catch{}return`${q}://${Z}/tunnel/${m}`}var y=null,v=null;function le(){return k(O,{onConnect(n){i("Connected to session-proxy via IPC"),y=n,v=new $(t=>{s&&s.readyState===N.OPEN&&s.send(t)},t=>{s&&s.readyState===N.OPEN&&s.send(JSON.stringify(t))}),n.on("data",t=>v.feed(t))},onClose(){i("IPC connection to session-proxy lost, will retry..."),y=null,v=null},onError(n){n.code!=="ENOENT"&&n.code!=="ECONNREFUSED"&&i(`IPC error: ${n.message}`)}})}var s=null,w=null,p=0,_=null;function J(e=re){if(s){try{s.terminate()}catch{}s=null}_&&(clearTimeout(_),_=null),_=setTimeout(()=>{_=null,M()},e)}function M(){let e=++p,n=ae();i(`Connecting to ${n}... (gen=${e}, sandbox=${m})`);let t={},r=se();if(r&&(t.Authorization=`Bearer ${r}`),s){try{s.terminate()}catch{}s=null}let c=new N(n,{headers:t});s=c;let o=null,u=!0,a=setTimeout(()=>{if(e===p){i(`Handshake timeout after ${U}ms \u2014 aborting and reconnecting`);try{c.terminate()}catch{}d()}},U);function d(){a&&(clearTimeout(a),a=null),o&&(clearInterval(o),o=null),e===p&&J()}function E(){u=!0,o=setInterval(()=>{if(e!==p){clearInterval(o);return}if(!u){i("No pong received \u2014 connection dead, reconnecting"),d();return}u=!1;try{c.ping()}catch{d()}},oe)}c.on("open",()=>{if(e!==p){c.terminate();return}a&&(clearTimeout(a),a=null),i(`Connected${w?` (reconnect, port ${w})`:""} (gen=${e})`),E()}),c.on("pong",()=>{e===p&&(u=!0)}),c.on("message",(l,f)=>{if(e===p){if(!f){try{ue(JSON.parse(l.toString()))}catch(S){i(`Bad control msg: ${S.message}`)}return}y&&!y.destroyed&&D(y,Buffer.from(l))}}),c.on("close",(l,f)=>{if(e!==p)return;let S=f?f.toString():"";if(l===4100&&(i(`Replaced by newer connection (code 4100, reason=${S}) \u2014 exiting`),process.exit(42)),l===4101){i(`Identity verification rejected (code 4101, reason=${S}) \u2014 retrying in ${F}ms (likely waiting for envd to update /run/e2b/.E2B_SANDBOX_ID)`),a&&(clearTimeout(a),a=null),o&&(clearInterval(o),o=null),e===p&&J(F);return}i(`Data WS closed: code=${l}, reason=${S}`),d()}),c.on("error",l=>{e===p&&(i(`Data WS error: ${l.message} (code=${l.code||"none"})`),d())}),c.on("unexpected-response",(l,f)=>{e===p&&(i(`Data WS unexpected response: status=${f.statusCode}`),d())})}function ue(e){if(e.type==="port"){w=e.port,i(`Assigned port ${e.port}`);return}if(e.type==="name"){if(i(`Name assigned: ${e.name}`),e.name&&/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(e.name)){let n=`${e.name}.local`;try{W("hostname",[n],{timeout:5e3,stdio:"ignore"}),h.writeFileSync("/etc/hostname",n+`
6
+ `)}catch{}try{H(n)}catch(t){i(`Failed to update /etc/hosts: ${t.message}`)}try{h.writeFileSync(ne,e.name)}catch{}}return}y&&!y.destroyed&&C(y,e)}function K(){try{let e=T(`npm view ${Y} 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){i(`Update available: ${n} \u2192 ${e}, installing...`),T(`npm install -g --prefix /opt/sandbox ${Y}@latest`,{timeout:12e4,stdio:"inherit"});try{T('pkill -f "check-claude-active.*--daemon" 2>/dev/null',{shell:!0,timeout:5e3})}catch{}try{let t=h.openSync("/tmp/claude-keepalive.log","a");G("/opt/sandbox/bin/check-claude-active",["--daemon"],{detached:!0,stdio:["ignore",t,t]}).unref(),h.closeSync(t)}catch{}i("Update installed, exiting for restart..."),process.exit(0)}}catch{}}function de(){let e=j.createServer((t,r)=>{if(r.setHeader("Content-Type","application/json"),t.method==="GET"&&t.url==="/health"){r.end(JSON.stringify({ok:!0,sandboxId:x(),wsConnected:!!(s&&s.readyState===N.OPEN)}));return}if(t.method==="POST"&&t.url==="/_relay/verify"){let c="";t.on("data",o=>{c+=o,c.length>4096&&t.destroy()}),t.on("end",()=>{let o;try{o=JSON.parse(c).nonce}catch{r.writeHead(400),r.end('{"error":"bad json"}');return}if(typeof o!="string"||!o.length||o.length>256){r.writeHead(400),r.end('{"error":"missing or invalid nonce"}');return}if(!s||s.readyState!==N.OPEN){r.writeHead(503),r.end('{"error":"WS not connected"}');return}try{s.send(JSON.stringify({type:"verify-ack",nonce:o}))}catch(u){r.writeHead(500),r.end(`{"error":"${u.message}"}`);return}r.writeHead(200),r.end('{"ok":true}')}),t.on("error",()=>{try{r.writeHead(400),r.end('{"error":"req error"}')}catch{}});return}r.writeHead(404),r.end('{"error":"not found"}')}),n=!1;return e.on("error",t=>{if(t.code==="EADDRINUSE"&&!n){n=!0,i(`Port ${I} held by another process \u2014 killing it (likely a pre-0.5.5 session-proxy that owned this port; watchdog will restart it under new code which no longer binds it)`);try{T("pkill -f /opt/sandbox/lib/node_modules/@typhons/sandbox-tools/session-proxy.js",{timeout:3e3})}catch{}setTimeout(()=>{try{e.listen(I,"0.0.0.0")}catch{}},1500);return}i(`Keepalive HTTP server error: ${t.message}`)}),e.listen(I,"0.0.0.0",()=>{i(`Keepalive HTTP server on :${I}`)}),e}var xe=le();de();M();ce||(setTimeout(K,1e4),setInterval(K,ie));i(`Relay client started (sandbox=${x()})`);
package/stop-agents.js CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import{execSync as e}from"child_process";import l from"fs";function n(t){console.log(`[stop-agents] ${t}`)}function r(t,d){let o;try{o=e(`pgrep -f "${d}" 2>/dev/null | head -1`,{encoding:"utf8"}).trim()}catch{}if(!o){n(`No ${t} process found`);return}let c;try{c=l.readlinkSync(`/proc/${o}/fd/0`)}catch{}if(!c||!c.startsWith("/dev/pts/")){n(`${t} (pid ${o}) has no PTY (fd/0 -> ${c}), skipping`);return}n(`Injecting Escape into ${t} (pid ${o}, tty ${c})`);try{e(`sudo python3 -c "
2
+ import{execSync as s}from"child_process";import p from"fs";import{execSync as d}from"child_process";function r(n){let o="";try{o=d(`pgrep -x ${n} 2>/dev/null`,{encoding:"utf8"})}catch{}return o.split(`
3
+ `).map(t=>t.trim()).filter(Boolean)}function c(n){console.log(`[stop-agents] ${n}`)}function l(n,o){let t;try{t=p.readlinkSync(`/proc/${o}/fd/0`)}catch{}if(!t||!t.startsWith("/dev/pts/")){c(`${n} (pid ${o}) has no PTY (fd/0 -> ${t}), skipping`);return}c(`Injecting Escape into ${n} (pid ${o}, tty ${t})`);try{s(`sudo python3 -c "
3
4
  import fcntl, os, struct
4
- fd = os.open('${c}', os.O_WRONLY)
5
+ fd = os.open('${t}', os.O_WRONLY)
5
6
  fcntl.ioctl(fd, 0x5412, struct.pack('b', 0x1b))
6
7
  os.close(fd)
7
- "`,{stdio:"ignore"}),n(`Escape injected for ${t}`)}catch{n(`TIOCSTI failed for ${t}, falling back to killing children`);try{let s=e(`pgrep -P ${o} 2>/dev/null`,{encoding:"utf8"}).trim().split(`
8
- `);for(let i of s)if(i)try{process.kill(parseInt(i),"SIGINT")}catch{}}catch{}}}r("Claude Code","^claude$");r("Codex","@openai/codex");n("Done");
8
+ "`,{stdio:"ignore"}),c(`Escape injected for ${n} (pid ${o})`)}catch{c(`TIOCSTI failed for ${n} (pid ${o}), falling back to SIGINT-ing children`);try{let e=s(`pgrep -P ${o} 2>/dev/null`,{encoding:"utf8"}).trim().split(`
9
+ `);for(let i of e)if(i)try{process.kill(parseInt(i),"SIGINT")}catch{}}catch{}}}function f(n,o){let t=r(o);if(t.length===0){c(`No ${n} process found`);return}c(`Found ${t.length} ${n} instance${t.length===1?"":"s"}: ${t.join(", ")}`);for(let e of t)l(n,e)}f("Claude Code","claude");f("Codex","codex");c("Done");