@shogo-ai/worker 2.5.3 → 2.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs
CHANGED
|
@@ -1,31 +1,34 @@
|
|
|
1
|
-
import{createRequire as Yr}from"node:module";var ht=(e,t,r)=>()=>{if(e)try{t=e(e=0)}catch(o){r=[o]}if(r)throw r[0];return t};var Oe=Yr(import.meta.url);import*as P from"node:child_process";import{promisify as St}from"node:util";function bt(e){return!!e&&typeof e==="object"&&!Array.isArray(e)}function kt(e){if(e.windowsHide!==void 0)return e;return{...e,windowsHide:!0}}function Rt(e){let t=e.slice(),r=t[1];if(t.length<2||typeof r==="function")return t.splice(1,0,{windowsHide:!0}),t;if(r==null)return t[1]={windowsHide:!0},t;if(bt(r))return t[1]=kt(r),t;return t}function ie(e){let t=e.slice(),r=-1;for(let i=1;i<t.length;i++)if(typeof t[i]==="function"){r=i;break}let o=r===-1?t.length:r;for(let i=1;i<o;i++)if(bt(t[i]))return t[i]=kt(t[i]),t;let n=o>1&&(Array.isArray(t[1])||t[1]==null)?2:1;if(n>=o)t.splice(o,0,{windowsHide:!0});else if(t[n]==null)t[n]={windowsHide:!0};return t}function V(e,t){let r=function(...o){let n=t();return Reflect.apply(n,this,e(o))};return Object.defineProperty(r,vt,{enumerable:!1,configurable:!0,writable:!1,value(...o){let n=t(),i=n[vt];if(typeof i==="function")return Reflect.apply(i,n,e(o));let s=St(n);return Reflect.apply(s,this,e(o))}}),r}var vt,T,we,Wi,Ui,se,Ue,Li;var W=ht(()=>{vt=St.custom;T=V(ie,()=>P.spawn),we=V(ie,()=>P.spawnSync),Wi=V(Rt,()=>P.exec),Ui=V(Rt,()=>P.execSync),se=V(ie,()=>P.execFile),Ue=V(ie,()=>P.execFileSync),Li=V(ie,()=>P.fork)});import{existsSync as Je}from"node:fs";import{join as Ve}from"node:path";import{promisify as Eo}from"node:util";async function Kt(e=!1){if(le!==null&&!e)return le;try{await Mo("git",["--version"],{timeout:3000}),le=!0}catch{le=!1}return le}function be(e,t){return`${e.replace(/\/+$/,"")}/api/projects/${t}/git`}function I(e,t={}){let{cwd:r,env:o,timeoutMs:n=300000,logger:i}=t;return new Promise((s,a)=>{let l=T("git",e,{cwd:r,env:{...process.env,...o},stdio:["ignore","pipe","pipe"]}),c="",u="",p=[],m=[];l.stdout.setEncoding("utf-8"),l.stderr.setEncoding("utf-8"),l.stdout.on("data",(y)=>{if(p.push(y),i)i.log(y.trimEnd())}),l.stderr.on("data",(y)=>{if(m.push(y),i)i.warn(y.trimEnd())});let f=setTimeout(()=>{try{l.kill("SIGKILL")}catch{}a(Error(`git ${e[0]} timed out after ${n}ms`))},n);l.on("error",(y)=>{clearTimeout(f),a(y)}),l.on("close",(y)=>{clearTimeout(f),c=p.join(""),u=m.join("");let g=y??-1;if(g===0)s({stdout:c,stderr:u,exitCode:g});else{let d=Error(`git ${e[0]} exited with code ${g}: ${u.slice(0,500)}`);d.exitCode=g,d.stdout=c,d.stderr=u,a(d)}})})}async function Jt(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,shallow:i=!0,logger:s,timeoutMs:a}=e;if(Je(Ve(n,".git")))throw Error(`cloneProject: ${n}/.git already exists; use gitFetchAndReset instead`);let l=be(t,o),c=["-c",`http.extraHeader=Authorization: Bearer ${r}`,"clone"];if(i)c.push("--depth=1");return c.push(l,n),await I(c,{logger:s,timeoutMs:a}),{commitSha:(await I(["rev-parse","HEAD"],{cwd:n,logger:s,timeoutMs:1e4})).stdout.trim()}}async function ke(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,branch:i="HEAD",logger:s,timeoutMs:a}=e,l=be(t,o),c=["-c",`http.extraHeader=Authorization: Bearer ${r}`];return await I([...c,"fetch",l,i],{cwd:n,logger:s,timeoutMs:a}),await I(["reset","--hard","FETCH_HEAD"],{cwd:n,logger:s,timeoutMs:a}),{commitSha:(await I(["rev-parse","HEAD"],{cwd:n,logger:s,timeoutMs:1e4})).stdout.trim()}}async function qe(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,logger:i,timeoutMs:s}=e,a=be(t,o),l=["-c",`http.extraHeader=Authorization: Bearer ${r}`];if(Je(Ve(n,".git","shallow")))await I([...l,"fetch","--unshallow",a],{cwd:n,logger:i,timeoutMs:s})}async function Ao(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,message:i,branch:s="HEAD",logger:a,timeoutMs:l}=e,c=be(t,o),u={GIT_AUTHOR_NAME:Ge,GIT_AUTHOR_EMAIL:Ke,GIT_COMMITTER_NAME:Ge,GIT_COMMITTER_EMAIL:Ke};await I(["add","-A"],{cwd:n,env:u,logger:a,timeoutMs:l});let p=!1;try{await I(["diff","--cached","--quiet"],{cwd:n,env:u,logger:a,timeoutMs:l})}catch{p=!0}if(!p)return{committed:!1};await I(["commit","-m",i,"--no-verify","--trailer",_o],{cwd:n,env:u,logger:a,timeoutMs:l});let f=(await I(["rev-parse","HEAD"],{cwd:n,env:u,logger:a,timeoutMs:1e4})).stdout.trim(),y=["-c",`http.extraHeader=Authorization: Bearer ${r}`];return await I([...y,"push",c,s],{cwd:n,env:u,logger:a,timeoutMs:l}),{committed:!0,commitSha:f}}function Re(e){return Je(Ve(e,".git"))}var Mo,Ge="Shogo Agent",Ke="agent@shogo.ai",_o,le=null;var Pe=ht(()=>{W();Mo=Eo(se),_o=`Co-authored-by: ${Ge} <${Ke}>`});import{Command as ki}from"commander";import Vr from"picocolors";import h from"picocolors";import{existsSync as kn}from"node:fs";import{readFileSync as zr,writeFileSync as Zr,existsSync as Qr,chmodSync as eo}from"node:fs";import{hostname as to}from"node:os";import{homedir as Xr}from"node:os";import{join as E}from"node:path";import{mkdirSync as ge}from"node:fs";var j=E(Xr(),".shogo"),H=E(j,"config.json"),Ei=E(j,"credentials.json"),ee=E(j,"device-id"),te=E(j,"worker.pid"),De=E(j,"logs"),he=E(De,"worker.log"),fe=E(De,"worker.err.log"),re=E(j,"runtime"),O=E(re,process.platform==="win32"?"agent-runtime.exe":"agent-runtime"),oe=E(re,"version.json"),ye=E(j,"projects");function z(e,t=ye){return E(t,e)}function B(){ge(j,{recursive:!0,mode:448}),ge(De,{recursive:!0,mode:448})}function ft(){B(),ge(re,{recursive:!0,mode:448})}function yt(e=ye){B(),ge(e,{recursive:!0,mode:448})}var We={cloudUrl:"https://studio.shogo.ai",port:8002,projectsDir:ye};function G(){if(!Qr(H))return{};try{return JSON.parse(zr(H,"utf-8"))}catch(e){throw Error(`Corrupt config at ${H}: ${e.message}`)}}function ne(e){B(),Zr(H,JSON.stringify(e,null,2),{mode:384}),eo(H,384)}function wt(e,t){return{...e,...Object.fromEntries(Object.entries(t).filter(([,r])=>r!==void 0))}}function D(e={}){let t=G(),r={apiKey:process.env.SHOGO_API_KEY,cloudUrl:process.env.SHOGO_CLOUD_URL,name:process.env.SHOGO_INSTANCE_NAME,workerDir:process.env.SHOGO_WORKER_DIR,port:process.env.PORT?parseInt(process.env.PORT,10):void 0,projectsDir:process.env.SHOGO_PROJECTS_DIR},o=wt(wt(t,r),e);if(!o.apiKey)throw Error("No API key. Run `shogo login` or pass --api-key / set SHOGO_API_KEY.");return{apiKey:o.apiKey,cloudUrl:o.cloudUrl??We.cloudUrl,name:o.name??to(),workerDir:o.workerDir??process.cwd(),port:o.port??We.port,projectsDir:o.projectsDir??We.projectsDir}}W();import{existsSync as ro,openSync as Pt,readFileSync as oo,unlinkSync as no,writeFileSync as io}from"node:fs";function ve(){if(!ro(te))return null;let e=oo(te,"utf-8").trim(),t=parseInt(e,10);return Number.isFinite(t)?t:null}function Se(e){try{return process.kill(e,0),!0}catch{return!1}}function Le(){try{no(te)}catch{}}function Tt(e){B();let t=ve();if(t&&Se(t))throw Error(`Worker already running (pid=${t}). Run \`shogo worker stop\` first.`);if(t)Le();let r=e.inheritStdio?["ignore","inherit","inherit"]:["ignore",Pt(he,"a"),Pt(fe,"a")],o=T(e.runner,[e.entry],{cwd:e.cwd,env:e.env,detached:!!e.detach,stdio:r});if(!o.pid)throw Error("Failed to spawn worker process.");if(io(te,String(o.pid),{mode:384}),e.detach)o.unref();return{pid:o.pid,child:o}}function $t(e="SIGTERM"){let t=ve();if(!t)return{killedPid:null};if(!Se(t))return Le(),{killedPid:null};try{process.kill(t,e)}catch{}return Le(),{killedPid:t}}import M from"picocolors";import{existsSync as ao}from"node:fs";import{request as so}from"node:http";function xt(e,t=process.env){let r=(i)=>i&&i.trim().length>0?i.trim():void 0,o=r(e);if(o)return{url:It(o),source:"flag"};let n=[["HTTPS_PROXY",t.HTTPS_PROXY],["https_proxy",t.https_proxy],["HTTP_PROXY",t.HTTP_PROXY],["http_proxy",t.http_proxy]];for(let[i,s]of n){let a=r(s);if(a)return{url:It(a),source:i}}return null}function It(e){if(/^https?:\/\//i.test(e))return e;return`http://${e}`}function Fe(e,t){if(!t)return e;return{...e,HTTPS_PROXY:e.HTTPS_PROXY??t.url,https_proxy:e.https_proxy??t.url,HTTP_PROXY:e.HTTP_PROXY??t.url,http_proxy:e.http_proxy??t.url}}async function Et(e,t="api.shogo.ai",r=5000){return new Promise((o)=>{let n=!1,i=(s)=>{if(n)return;n=!0,o(s)};try{let s=new URL(e.url),a=s.hostname,l=s.port?parseInt(s.port,10):s.protocol==="https:"?443:80,c=so({method:"CONNECT",host:a,port:l,path:`${t}:443`,timeout:r});c.on("connect",(u)=>{c.destroy();let p=u.statusCode??0;if(p===200)i({ok:!0,detail:`CONNECT ${t}:443 → 200 (via ${a}:${l})`});else if(p===407)i({ok:!1,detail:`407 Proxy Authentication Required — check credentials in ${e.source}`});else i({ok:!1,detail:`CONNECT → HTTP ${p}`})}),c.on("timeout",()=>{c.destroy(),i({ok:!1,detail:`timeout after ${r}ms`})}),c.on("error",(u)=>{let p=u.code?` (${u.code})`:"";i({ok:!1,detail:`${u.message}${p}`})}),c.end()}catch(s){i({ok:!1,detail:s?.message??"unknown error"})}})}function Mt(e){let t;try{t=new URL(e)}catch{return[]}let r=t.host,o=t.protocol.replace(":","")||"https",n=t.hostname.split("."),i=n.length>=2?n.slice(-2).join("."):t.hostname;return[{url:`${o}://${r}`,host:r,purpose:"control",criticality:"fatal"},{url:`${o}://api-direct.${i}`,host:`api-direct.${i}`,purpose:"tunnel-direct",criticality:"graceful"},{url:`${o}://artifacts.${i}`,host:`artifacts.${i}`,purpose:"artifacts",criticality:"graceful"}]}var lo=5000;async function co(e,t){try{let r=new AbortController,o=setTimeout(()=>r.abort(),t),n=await fetch(`${e.replace(/\/$/,"")}/health`,{signal:r.signal}).catch(()=>null);if(clearTimeout(o),!n)return{ok:!1,detail:"no response (firewall? DNS?)"};return{ok:!0,detail:`HTTP ${n.status}`}}catch(r){return{ok:!1,detail:r?.message??"unknown error"}}}var _t=(e)=>{let t=[{name:"Runtime (node >= 20)",criticality:"fatal",async run(){let[o]=process.versions.node.split(".").map(Number);return(o??0)>=20?{ok:!0,detail:`node v${process.versions.node}`}:{ok:!1,detail:`node v${process.versions.node} — need >=20`}}},{name:"Worker directory exists",criticality:"fatal",async run(){return ao(e.workerDir)?{ok:!0,detail:e.workerDir}:{ok:!1,detail:`${e.workerDir} does not exist`}}}];if(e.proxy)t.push({name:`Proxy reachable (${uo(e.proxy.url)})`,criticality:"fatal",async run(){return Et(e.proxy)}});let r=Mt(e.cloudUrl);for(let o of r)t.push({name:`Reach ${o.host}${o.purpose!=="control"?M.dim(` (${o.purpose})`):""}`,criticality:o.criticality,run:()=>co(o.url,lo)});return t.push({name:"API key valid",criticality:"fatal",async run(){try{let o=await fetch(`${e.cloudUrl}/api/instances/heartbeat`,{method:"POST",headers:{"content-type":"application/json","x-api-key":e.apiKey},body:JSON.stringify({hostname:"preflight",name:"preflight",os:"preflight",arch:"preflight",metadata:{preflight:!0}})});if(o.status===401||o.status===403)return{ok:!1,detail:`HTTP ${o.status} — key rejected`};return o.ok?{ok:!0,detail:`HTTP ${o.status}`}:{ok:!1,detail:`HTTP ${o.status}`}}catch(o){return{ok:!1,detail:o?.message??"unknown error"}}}}),t};function uo(e){try{return new URL(e).host}catch{return e}}async function At(e){console.log(M.bold(`
|
|
1
|
+
import{createRequire as Xr}from"node:module";var ft=(e,t,r)=>()=>{if(e)try{t=e(e=0)}catch(o){r=[o]}if(r)throw r[0];return t};var Ce=Xr(import.meta.url);import*as R from"node:child_process";import{promisify as St}from"node:util";function Pt(e){return!!e&&typeof e==="object"&&!Array.isArray(e)}function kt(e){if(e.windowsHide!==void 0)return e;return{...e,windowsHide:!0}}function Rt(e){let t=e.slice(),r=t[1];if(t.length<2||typeof r==="function")return t.splice(1,0,{windowsHide:!0}),t;if(r==null)return t[1]={windowsHide:!0},t;if(Pt(r))return t[1]=kt(r),t;return t}function se(e){let t=e.slice(),r=-1;for(let i=1;i<t.length;i++)if(typeof t[i]==="function"){r=i;break}let o=r===-1?t.length:r;for(let i=1;i<o;i++)if(Pt(t[i]))return t[i]=kt(t[i]),t;let n=o>1&&(Array.isArray(t[1])||t[1]==null)?2:1;if(n>=o)t.splice(o,0,{windowsHide:!0});else if(t[n]==null)t[n]={windowsHide:!0};return t}function V(e,t){let r=function(...o){let n=t();return Reflect.apply(n,this,e(o))};return Object.defineProperty(r,bt,{enumerable:!1,configurable:!0,writable:!1,value(...o){let n=t(),i=n[bt];if(typeof i==="function")return Reflect.apply(i,n,e(o));let s=St(n);return Reflect.apply(s,this,e(o))}}),r}var bt,T,we,Ki,Ji,q,We,Vi;var W=ft(()=>{bt=St.custom;T=V(se,()=>R.spawn),we=V(se,()=>R.spawnSync),Ki=V(Rt,()=>R.exec),Ji=V(Rt,()=>R.execSync),q=V(se,()=>R.execFile),We=V(se,()=>R.execFileSync),Vi=V(se,()=>R.fork)});import{existsSync as Ke}from"node:fs";import{join as Je}from"node:path";import{promisify as _o}from"node:util";async function Jt(e=!1){if(le!==null&&!e)return le;try{await Ao("git",["--version"],{timeout:3000}),le=!0}catch{le=!1}return le}function Se(e,t){return`${e.replace(/\/+$/,"")}/api/projects/${t}/git`}function I(e,t={}){let{cwd:r,env:o,timeoutMs:n=300000,logger:i}=t;return new Promise((s,a)=>{let l=T("git",e,{cwd:r,env:{...process.env,...o},stdio:["ignore","pipe","pipe"]}),c="",p="",u=[],m=[];l.stdout.setEncoding("utf-8"),l.stderr.setEncoding("utf-8"),l.stdout.on("data",(y)=>{if(u.push(y),i)i.log(y.trimEnd())}),l.stderr.on("data",(y)=>{if(m.push(y),i)i.warn(y.trimEnd())});let f=setTimeout(()=>{try{l.kill("SIGKILL")}catch{}a(Error(`git ${e[0]} timed out after ${n}ms`))},n);l.on("error",(y)=>{clearTimeout(f),a(y)}),l.on("close",(y)=>{clearTimeout(f),c=u.join(""),p=m.join("");let h=y??-1;if(h===0)s({stdout:c,stderr:p,exitCode:h});else{let d=Error(`git ${e[0]} exited with code ${h}: ${p.slice(0,500)}`);d.exitCode=h,d.stdout=c,d.stderr=p,a(d)}})})}async function Vt(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,shallow:i=!0,logger:s,timeoutMs:a}=e;if(Ke(Je(n,".git")))throw Error(`cloneProject: ${n}/.git already exists; use gitFetchAndReset instead`);let l=Se(t,o),c=["-c",`http.extraHeader=Authorization: Bearer ${r}`,"clone"];if(i)c.push("--depth=1");return c.push(l,n),await I(c,{logger:s,timeoutMs:a}),{commitSha:(await I(["rev-parse","HEAD"],{cwd:n,logger:s,timeoutMs:1e4})).stdout.trim()}}async function Pe(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,branch:i="HEAD",logger:s,timeoutMs:a}=e,l=Se(t,o),c=["-c",`http.extraHeader=Authorization: Bearer ${r}`];return await I([...c,"fetch",l,i],{cwd:n,logger:s,timeoutMs:a}),await I(["reset","--hard","FETCH_HEAD"],{cwd:n,logger:s,timeoutMs:a}),{commitSha:(await I(["rev-parse","HEAD"],{cwd:n,logger:s,timeoutMs:1e4})).stdout.trim()}}async function Ve(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,logger:i,timeoutMs:s}=e,a=Se(t,o),l=["-c",`http.extraHeader=Authorization: Bearer ${r}`];if(Ke(Je(n,".git","shallow")))await I([...l,"fetch","--unshallow",a],{cwd:n,logger:i,timeoutMs:s})}async function Co(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,message:i,branch:s="HEAD",logger:a,timeoutMs:l}=e,c=Se(t,o),p={GIT_AUTHOR_NAME:Be,GIT_AUTHOR_EMAIL:Ge,GIT_COMMITTER_NAME:Be,GIT_COMMITTER_EMAIL:Ge};await I(["add","-A"],{cwd:n,env:p,logger:a,timeoutMs:l});let u=!1;try{await I(["diff","--cached","--quiet"],{cwd:n,env:p,logger:a,timeoutMs:l})}catch{u=!0}if(!u)return{committed:!1};await I(["commit","-m",i,"--no-verify","--trailer",Mo],{cwd:n,env:p,logger:a,timeoutMs:l});let f=(await I(["rev-parse","HEAD"],{cwd:n,env:p,logger:a,timeoutMs:1e4})).stdout.trim(),y=["-c",`http.extraHeader=Authorization: Bearer ${r}`];return await I([...y,"push",c,s],{cwd:n,env:p,logger:a,timeoutMs:l}),{committed:!0,commitSha:f}}function ke(e){return Ke(Je(e,".git"))}var Ao,Be="Shogo Agent",Ge="agent@shogo.ai",Mo,le=null;var Re=ft(()=>{W();Ao=_o(q),Mo=`Co-authored-by: ${Be} <${Ge}>`});import{Command as Ai}from"commander";import qr from"picocolors";import g from"picocolors";import{existsSync as An}from"node:fs";import{readFileSync as Zr,writeFileSync as Qr,existsSync as eo,chmodSync as to}from"node:fs";import{hostname as ro}from"node:os";import{homedir as zr}from"node:os";import{join as E}from"node:path";import{mkdirSync as he}from"node:fs";var j=E(zr(),".shogo"),H=E(j,"config.json"),Fi=E(j,"credentials.json"),te=E(j,"device-id"),re=E(j,"worker.pid"),Oe=E(j,"logs"),ge=E(Oe,"worker.log"),fe=E(Oe,"worker.err.log"),oe=E(j,"runtime"),O=E(oe,process.platform==="win32"?"agent-runtime.exe":"agent-runtime"),ne=E(oe,"version.json"),ye=E(j,"projects");function Z(e,t=ye){return E(t,e)}function B(){he(j,{recursive:!0,mode:448}),he(Oe,{recursive:!0,mode:448})}function yt(){B(),he(oe,{recursive:!0,mode:448})}function wt(e=ye){B(),he(e,{recursive:!0,mode:448})}var De={cloudUrl:"https://studio.shogo.ai",port:8002,projectsDir:ye};function G(){if(!eo(H))return{};try{return JSON.parse(Zr(H,"utf-8"))}catch(e){throw Error(`Corrupt config at ${H}: ${e.message}`)}}function ie(e){B(),Qr(H,JSON.stringify(e,null,2),{mode:384}),to(H,384)}function vt(e,t){return{...e,...Object.fromEntries(Object.entries(t).filter(([,r])=>r!==void 0))}}function D(e={}){let t=G(),r={apiKey:process.env.SHOGO_API_KEY,cloudUrl:process.env.SHOGO_CLOUD_URL,name:process.env.SHOGO_INSTANCE_NAME,workerDir:process.env.SHOGO_WORKER_DIR,port:process.env.PORT?parseInt(process.env.PORT,10):void 0,projectsDir:process.env.SHOGO_PROJECTS_DIR},o=vt(vt(t,r),e);if(!o.apiKey)throw Error("No API key. Run `shogo login` or pass --api-key / set SHOGO_API_KEY.");return{apiKey:o.apiKey,cloudUrl:o.cloudUrl??De.cloudUrl,name:o.name??ro(),workerDir:o.workerDir??process.cwd(),port:o.port??De.port,projectsDir:o.projectsDir??De.projectsDir}}W();import{existsSync as oo,openSync as Tt,readFileSync as no,unlinkSync as io,writeFileSync as so}from"node:fs";function ve(){if(!oo(re))return null;let e=no(re,"utf-8").trim(),t=parseInt(e,10);return Number.isFinite(t)?t:null}function be(e){try{return process.kill(e,0),!0}catch{return!1}}function Ue(){try{io(re)}catch{}}function $t(e){B();let t=ve();if(t&&be(t))throw Error(`Worker already running (pid=${t}). Run \`shogo worker stop\` first.`);if(t)Ue();let r=e.inheritStdio?["ignore","inherit","inherit"]:["ignore",Tt(ge,"a"),Tt(fe,"a")],o=T(e.runner,[e.entry],{cwd:e.cwd,env:e.env,detached:!!e.detach,stdio:r});if(!o.pid)throw Error("Failed to spawn worker process.");if(so(re,String(o.pid),{mode:384}),e.detach)o.unref();return{pid:o.pid,child:o}}function It(e="SIGTERM"){let t=ve();if(!t)return{killedPid:null};if(!be(t))return Ue(),{killedPid:null};try{process.kill(t,e)}catch{}return Ue(),{killedPid:t}}import _ from"picocolors";import{existsSync as lo}from"node:fs";import{request as ao}from"node:http";function Et(e,t=process.env){let r=(i)=>i&&i.trim().length>0?i.trim():void 0,o=r(e);if(o)return{url:xt(o),source:"flag"};let n=[["HTTPS_PROXY",t.HTTPS_PROXY],["https_proxy",t.https_proxy],["HTTP_PROXY",t.HTTP_PROXY],["http_proxy",t.http_proxy]];for(let[i,s]of n){let a=r(s);if(a)return{url:xt(a),source:i}}return null}function xt(e){if(/^https?:\/\//i.test(e))return e;return`http://${e}`}function Fe(e,t){if(!t)return e;return{...e,HTTPS_PROXY:e.HTTPS_PROXY??t.url,https_proxy:e.https_proxy??t.url,HTTP_PROXY:e.HTTP_PROXY??t.url,http_proxy:e.http_proxy??t.url}}async function _t(e,t="api.shogo.ai",r=5000){return new Promise((o)=>{let n=!1,i=(s)=>{if(n)return;n=!0,o(s)};try{let s=new URL(e.url),a=s.hostname,l=s.port?parseInt(s.port,10):s.protocol==="https:"?443:80,c=ao({method:"CONNECT",host:a,port:l,path:`${t}:443`,timeout:r});c.on("connect",(p)=>{c.destroy();let u=p.statusCode??0;if(u===200)i({ok:!0,detail:`CONNECT ${t}:443 → 200 (via ${a}:${l})`});else if(u===407)i({ok:!1,detail:`407 Proxy Authentication Required — check credentials in ${e.source}`});else i({ok:!1,detail:`CONNECT → HTTP ${u}`})}),c.on("timeout",()=>{c.destroy(),i({ok:!1,detail:`timeout after ${r}ms`})}),c.on("error",(p)=>{let u=p.code?` (${p.code})`:"";i({ok:!1,detail:`${p.message}${u}`})}),c.end()}catch(s){i({ok:!1,detail:s?.message??"unknown error"})}})}function At(e){let t;try{t=new URL(e)}catch{return[]}let r=t.host,o=t.protocol.replace(":","")||"https",n=t.hostname.split("."),i=n.length>=2?n.slice(-2).join("."):t.hostname;return[{url:`${o}://${r}`,host:r,purpose:"control",criticality:"fatal"},{url:`${o}://api-direct.${i}`,host:`api-direct.${i}`,purpose:"tunnel-direct",criticality:"graceful"},{url:`${o}://artifacts.${i}`,host:`artifacts.${i}`,purpose:"artifacts",criticality:"graceful"}]}var co=5000;async function uo(e,t){try{let r=new AbortController,o=setTimeout(()=>r.abort(),t),n=await fetch(`${e.replace(/\/$/,"")}/health`,{signal:r.signal}).catch(()=>null);if(clearTimeout(o),!n)return{ok:!1,detail:"no response (firewall? DNS?)"};return{ok:!0,detail:`HTTP ${n.status}`}}catch(r){return{ok:!1,detail:r?.message??"unknown error"}}}var Mt=(e)=>{let t=[{name:"Runtime (node >= 20)",criticality:"fatal",async run(){let[o]=process.versions.node.split(".").map(Number);return(o??0)>=20?{ok:!0,detail:`node v${process.versions.node}`}:{ok:!1,detail:`node v${process.versions.node} — need >=20`}}},{name:"Worker directory exists",criticality:"fatal",async run(){return lo(e.workerDir)?{ok:!0,detail:e.workerDir}:{ok:!1,detail:`${e.workerDir} does not exist`}}}];if(e.proxy)t.push({name:`Proxy reachable (${po(e.proxy.url)})`,criticality:"fatal",async run(){return _t(e.proxy)}});let r=At(e.cloudUrl);for(let o of r)t.push({name:`Reach ${o.host}${o.purpose!=="control"?_.dim(` (${o.purpose})`):""}`,criticality:o.criticality,run:()=>uo(o.url,co)});return t.push({name:"API key valid",criticality:"fatal",async run(){try{let o=await fetch(`${e.cloudUrl}/api/instances/heartbeat`,{method:"POST",headers:{"content-type":"application/json","x-api-key":e.apiKey},body:JSON.stringify({hostname:"preflight",name:"preflight",os:"preflight",arch:"preflight",metadata:{preflight:!0}})});if(o.status===401||o.status===403)return{ok:!1,detail:`HTTP ${o.status} — key rejected`};return o.ok?{ok:!0,detail:`HTTP ${o.status}`}:{ok:!1,detail:`HTTP ${o.status}`}}catch(o){return{ok:!1,detail:o?.message??"unknown error"}}}}),t};function po(e){try{return new URL(e).host}catch{return e}}async function Ct(e){console.log(_.bold(`
|
|
2
2
|
Shogo Worker — Preflight
|
|
3
|
-
`));let t=!1,r=0;for(let o of e){process.stdout.write(` ${
|
|
3
|
+
`));let t=!1,r=0;for(let o of e){process.stdout.write(` ${_.dim("...")} ${o.name}`);let n=await o.run();if(process.stdout.write("\r"),n.ok)console.log(` ${_.green("✓")} ${o.name}${n.detail?_.dim(` — ${n.detail}`):""}`);else if(o.criticality==="graceful")console.log(` ${_.yellow("◦")} ${o.name}${n.detail?_.dim(` — ${n.detail}`):""}${_.yellow(" (optional — worker will still start)")}`),r++;else console.log(` ${_.red("✗")} ${o.name}${n.detail?_.dim(` — ${n.detail}`):""}`),t=!0}if(!t&&r===0)console.log(_.green(`
|
|
4
4
|
All checks passed.
|
|
5
|
-
`));else if(!t)console.log(
|
|
5
|
+
`));else if(!t)console.log(_.yellow(`
|
|
6
6
|
Starting with ${r} optional host(s) blocked. See docs/my-machines-networking.md.
|
|
7
|
-
`));else console.log(
|
|
7
|
+
`));else console.log(_.red(`
|
|
8
8
|
Preflight failed — fix blocking issues before starting.
|
|
9
|
-
`));return!t}import{accessSync as
|
|
10
|
-
`)}W();import{createHmac as lr,randomBytes as Ho}from"node:crypto";import{existsSync as Ze,mkdirSync as Yt,readdirSync as cr,rmSync as Bo,unwatchFile as Go,watch as Ko,watchFile as Jo}from"node:fs";import{createConnection as Vo}from"node:net";import{tmpdir as Xt}from"node:os";import{basename as qo,dirname as zt,join as Y}from"node:path";W();import{existsSync as Wt,mkdirSync as vo,readFileSync as Ut,writeFileSync as ae}from"node:fs";import{join as U}from"node:path";var Dt=1e5;function So(e){try{let t=JSON.parse(Ut(e,"utf-8"));return t&&typeof t==="object"?t:null}catch{return null}}function bo(e){let t=parseInt(e.SHOGO_EXEC_MEMORY_MB||"",10);if(Number.isFinite(t)&&t>0)return t;if(!e.SHOGO_HOST_LIMITS_FILE)return 0;let r=So(e.SHOGO_HOST_LIMITS_FILE)?.execMemoryMB;return typeof r==="number"&&Number.isFinite(r)&&r>0?Math.floor(r):0}function He(e){let t=parseInt(e.RUNTIME_MEMORY_MB||"",10);if(!Number.isFinite(t)||t<=0)return null;let r=parseInt(e.RUNTIME_CPU_PERCENT||"0",10),o=Number.isFinite(r)&&r>0?r:0,n=parseInt(e.RUNTIME_TASKS_MAX||"",10),i=Number.isFinite(n)&&n>0?n:4096;return{memoryMB:t,osMemoryMB:t+bo(e),cpuPercent:o,tasksMax:i}}function Lt(e,t){let o=`--max-old-space-size=${Math.max(256,Math.floor(t*0.75))}`,n=e.NODE_OPTIONS?.trim();if(n){if(/--max-old-space-size=/.test(n))return;e.NODE_OPTIONS=`${n} ${o}`}else e.NODE_OPTIONS=o}function ko(){if(process.platform!=="linux")return!1;try{return we("systemd-run",["--version"],{stdio:"ignore",timeout:3000}).status===0}catch{return!1}}function Ft(e){let{command:t,args:r,limits:o,scopeName:n,logger:i}=e;if(process.platform!=="linux"||!ko())return{command:t,args:r,wrapped:!1};let s=["-p",`MemoryMax=${o.osMemoryMB}M`,"-p",`MemoryHigh=${Ne(o.osMemoryMB)}M`,"-p",`TasksMax=${o.tasksMax}`];if(o.cpuPercent>0)s.push("-p",`CPUQuota=${o.cpuPercent}%`);let a=["--user","--scope","--quiet","--collect",`--unit=${n}`,...s,"--",t,...r];return i?.log(`[resource-limits] wrapping spawn in systemd scope ${n} (MemoryMax=${o.osMemoryMB}M)`),{command:"systemd-run",args:a,wrapped:!0}}function Ne(e){return Math.max(128,Math.floor(e*0.9))}function Nt(e){return U("/sys/fs/cgroup","shogo",`rt-${e}`)}function jt(e){let{pid:t,limits:r,logger:o}=e;if(process.platform!=="linux")return!1;if(!Wt(U("/sys/fs/cgroup","cgroup.controllers")))return!1;let i=Nt(t);try{if(vo(i,{recursive:!0}),ae(U(i,"memory.max"),String(r.osMemoryMB*1024*1024)),r.cpuPercent>0){let s=Math.floor(r.cpuPercent/100*Dt);ae(U(i,"cpu.max"),`${s} ${Dt}`)}try{ae(U(i,"pids.max"),String(r.tasksMax))}catch{}return ae(U(i,"cgroup.procs"),String(t)),o?.log(`[resource-limits] attached pid ${t} to cgroup ${i} (memory.max=${r.osMemoryMB}M)`),!0}catch(s){return o?.warn(`[resource-limits] direct cgroup attach failed for pid ${t}: ${s?.message??s}`),!1}}function Ht(e,t={}){let r=t.platform??process.platform,{limits:o,logger:n}=e;if(r==="win32")return n?.log("[resource-limits] Job Object memory limit applies on the next runtime spawn"),!1;if(r!=="linux")return!1;if(e.scopeName){let l=["--user","set-property","--runtime",e.scopeName,`MemoryMax=${o.osMemoryMB}M`,`MemoryHigh=${Ne(o.osMemoryMB)}M`];if(t.run)return t.run("systemctl",l),!0;return se("systemctl",l,{timeout:5000},(c)=>{if(c)n?.warn(`[resource-limits] set-property ${e.scopeName} failed: ${c.message}`);else n?.log(`[resource-limits] ${e.scopeName} MemoryMax=${o.osMemoryMB}M`)}),!0}if(!e.pid)return!1;let i=Nt(e.pid),s=t.exists??Wt,a=t.writeFile??ae;try{if(!s(U(i,"memory.max")))return!1;if(a(U(i,"memory.max"),String(o.osMemoryMB*1024*1024)),s(U(i,"memory.high")))a(U(i,"memory.high"),String(Ne(o.osMemoryMB)*1024*1024));return n?.log(`[resource-limits] raised cgroup ${i} memory.max to ${o.osMemoryMB}M`),!0}catch(l){return n?.warn(`[resource-limits] live cgroup update failed for ${i}: ${l?.message??l}`),!1}}function Bt(e){let{pid:t,limits:r,logger:o}=e;if(process.platform!=="win32")return!1;let n=process.env.SHOGO_WINJOB_HELPER||"@shogo/winjob";try{let i=Oe(n);if(typeof i.limitProcess!=="function")return!1;let s=i.limitProcess(t,{memoryBytes:r.osMemoryMB*1024*1024,killOnClose:!0});if(s)o?.log(`[resource-limits] attached pid ${t} to Job Object (${r.osMemoryMB}M)`);return s}catch{return!1}}function Gt(e){try{let t=JSON.parse(Ut(e,"utf-8"));if(!Array.isArray(t.pids))return[];return t.pids.filter((r)=>Number.isInteger(r)&&r>0)}catch{return[]}}var Ro=20000,Po=`$ErrorActionPreference='SilentlyContinue';foreach($p in Get-CimInstance Win32_Process -Property ProcessId,ParentProcessId,WorkingSetSize){"$($p.ProcessId) $($p.ParentProcessId) $([math]::Floor($p.WorkingSetSize/1024))"}`;function To(e=process.platform){let[t,r]=e==="win32"?["powershell.exe",["-NoProfile","-NonInteractive","-Command",Po]]:["ps",["-A","-o","pid=,ppid=,pgid=,rss="]];return new Promise((o)=>{se(t,r,{encoding:"utf-8",timeout:Ro,maxBuffer:8388608},(n,i)=>{if(n||!i){o(null);return}let s=[];for(let a of String(i).split(/\r?\n/)){let l=a.trim().split(/\s+/).map((c)=>parseInt(c,10));if(l.some((c)=>!Number.isFinite(c)))continue;if(e==="win32"&&l.length===3)s.push({pid:l[0],ppid:l[1],pgid:0,kb:l[2]});else if(e!=="win32"&&l.length===4)s.push({pid:l[0],ppid:l[1],pgid:l[2],kb:l[3]})}o(s.length?s:null)})})}function $o(e,t,r=process.platform,o=[]){if(r!=="win32"){let u=e.filter((p)=>p.pgid===t);return u.length?u.reduce((p,m)=>p+m.kb,0):null}if(!e.some((u)=>u.pid===t))return null;let n=new Set(o.filter((u)=>u!==t)),i=new Map;for(let u of e){if(u.pid===u.ppid)continue;let p=i.get(u.ppid);if(p)p.push(u);else i.set(u.ppid,[u])}let s=new Map(e.map((u)=>[u.pid,u])),a=new Set,l=[t],c=0;while(l.length){let u=l.pop();if(a.has(u))continue;a.add(u),c+=s.get(u)?.kb??0;for(let p of i.get(u)??[])if(!n.has(p.pid))l.push(p.pid)}return c}var je=new Map;function Io(e,t,r){let o=`${t}:${r}`,n=je.get(o);if(!n){let i={key:o,watchdogs:new Set,sampling:!1,timer:setInterval(()=>{xo(i,t)},r)};try{i.timer.unref?.()}catch{}je.set(o,i),n=i}return n.watchdogs.add(e),n}async function xo(e,t){if(e.sampling)return;e.sampling=!0;try{let r=await To(t);for(let o of[...e.watchdogs])o.observe(r)}finally{e.sampling=!1}}class Be{sampler=null;consecutive=0;fired=!1;pid;ceilingMB;platform;intervalMs;strikes;excludePids;logger;onBreach;constructor(e){this.pid=e.pid,this.ceilingMB=e.ceilingMB,this.platform=e.platform??process.platform,this.intervalMs=e.intervalMs??parseInt(process.env.RUNTIME_RSS_WATCHDOG_MS||"15000",10),this.strikes=e.strikes??parseInt(process.env.RUNTIME_RSS_WATCHDOG_STRIKES||"2",10),this.excludePids=e.excludePids,this.logger=e.logger,this.onBreach=e.onBreach}start(){if(this.sampler)return;this.sampler=Io(this,this.platform,Math.max(2000,this.intervalMs))}stop(){if(this.sampler?.watchdogs.delete(this),this.sampler?.watchdogs.size===0)clearInterval(this.sampler.timer),je.delete(this.sampler.key);this.sampler=null}observe(e){if(this.fired)return;let t=e?$o(e,this.pid,this.platform,this.excludePids?.()??[]):null;if(t==null){this.consecutive=0;return}let r=Math.floor(t/1024);if(r<=this.ceilingMB){this.consecutive=0;return}if(this.consecutive+=1,this.logger?.warn(`[resource-limits] pid ${this.pid} RSS ${r}MB over ceiling ${this.ceilingMB}MB (strike ${this.consecutive}/${this.strikes})`),this.consecutive>=this.strikes)this.fired=!0,this.stop(),this.onBreach(r)}}import{CloudFileTransport as Qe}from"@shogo-ai/sdk/cloud-file-transport";import{killViteWatchFromPidfile as Zt}from"@shogo-ai/sdk/vite-watch";import{reapChildLedger as Yo}from"@shogo-ai/sdk/child-ledger";import{watch as Vt,statSync as Co}from"node:fs";import{relative as ds,sep as Oo,posix as Do}from"node:path";var Wo=1500,Uo=new Set(["node_modules",".git","dist","build",".vite",".cache"]),Lo=[".shogo/"];function Fo(e){if(!e)return!0;return e.split("/").some((t)=>Uo.has(t))}class ce{rootDir;transport;debounceMs;logger;onFlush;mode;git;commitAndPush;watcher=null;pending=new Set;timer=null;flushing=!1;stopped=!1;constructor(e){if(this.rootDir=e.rootDir,this.transport=e.transport,this.debounceMs=e.debounceMs??Wo,this.logger=e.logger??console,this.onFlush=e.onFlush,this.mode=e.mode??"files",this.mode==="git"&&!e.git)throw Error('CloudSyncWatcher: mode: "git" requires the `git` option block');this.git=e.git,this.commitAndPush=e.commitAndPush??(async(t)=>{let r=await Promise.resolve().then(() => (Pe(),{}));return Ao(t)})}start(){if(this.watcher)return;if(this.stopped)throw Error("CloudSyncWatcher: already stopped, build a fresh instance");try{this.watcher=Vt(this.rootDir,{recursive:!0},(e,t)=>{if(!t)return;this.handleEvent(String(t))})}catch(e){this.logger.warn(`[CloudSyncWatcher] recursive watch failed (${e?.message??e}); falling back to root-only watch.`),this.watcher=Vt(this.rootDir,(t,r)=>{if(!r)return;this.handleEvent(String(r))})}}async stop(){if(this.stopped)return;if(this.stopped=!0,this.timer)clearTimeout(this.timer),this.timer=null;if(this.watcher){try{this.watcher.close()}catch{}this.watcher=null}if(this.pending.size>0)await this.flush()}handleEvent(e){if(this.stopped)return;let t=e.split(Oo).join(Do.sep);if(Fo(t))return;try{let r=`${this.rootDir}/${t}`;if(Co(r).isDirectory())return}catch{return}this.pending.add(t),this.scheduleFlush()}scheduleFlush(){if(this.timer)return;this.timer=setTimeout(()=>{this.timer=null,this.flush()},this.debounceMs)}async flush(){if(this.flushing){this.scheduleFlush();return}if(this.pending.size===0)return;this.flushing=!0;let e=Array.from(this.pending);this.pending.clear();try{if(this.mode==="git")await this.flushGit(e);else await this.flushFiles(e)}catch(t){this.logger.error(`[CloudSyncWatcher] flush failed: ${t?.message??t}`);for(let r of e)this.pending.add(r);this.scheduleFlush()}finally{this.flushing=!1}}async flushFiles(e){if(e.length===0)return;let t=await this.transport.uploadFiles(e);if(this.onFlush?.({uploaded:e,errors:t.errors.length}),t.errors.length>0)for(let r of t.errors)this.logger.warn(`[CloudSyncWatcher] upload ${r.path}: ${r.message}`)}async flushGit(e){if(!this.git)throw Error("CloudSyncWatcher: missing git options");let t=[],r=[];for(let i of e)if(Lo.some((s)=>i===s.slice(0,-1)||i.startsWith(s)))t.push(i);else r.push(i);if(t.length>0){let i=await this.transport.uploadFiles(t);if(i.errors.length>0)for(let s of i.errors)this.logger.warn(`[CloudSyncWatcher] upload ${s.path}: ${s.message}`)}let o=!1,n;if(r.length>0){let i=`auto: ${new Date().toISOString()}`;try{let s=await this.commitAndPush({apiUrl:this.git.apiUrl,apiKey:this.git.apiKey,projectId:this.git.projectId,localDir:this.rootDir,message:i,branch:this.git.branch,logger:this.logger});o=s.committed,n=s.commitSha}catch(s){throw s}}this.onFlush?.({uploaded:e,errors:0,committed:o,commitSha:n})}}Pe();var qt=1e4,Ye=2000;var Xe={turns:0,tasks:0,streams:0,children:0};function No(e){return`turns=${e.turns} tasks=${e.tasks} streams=${e.streams} children=${e.children}`}function jo(e){return e.turns+e.tasks+e.streams+e.children}function _(e,t,r,o,n){return{busy:e,dead:t,reason:r,counts:o,countsText:No(o),...n!=null?{sampleAgeMs:n}:{}}}function ze(e){let{now:t,startedAt:r,status:o,sample:n,processExited:i=!1,startupGraceMs:s=120000,staleMs:a=30000,deadAfterMs:l=60000}=e,c=e.terminalSessions??0,u=e.inflightCalls??0,p=n?.counts??Xe,m=n?Math.max(0,t-n.at):void 0;if(c>0)return _(!0,!1,"terminal",p,m);if(u>0)return _(!0,!1,"inflight-call",p,m);if(i)return _(!1,!0,"dead",p,m);if(o==="starting"||o==="restarting")return _(!0,!1,"grace",p,m);let f=t-(r||t);if(f>=0&&f<s)return _(!0,!1,"grace",p,m);if(o!=="running")return _(!0,!1,"unknown",p,m);if(!n)return _(!0,!1,"unknown",p,m);if(!n.ok){if((n.failingSince!=null?t-n.failingSince:0)>=l)return _(!1,!0,"dead",p,m);return _(!0,!1,"unknown",p,m)}if((m??0)>a)return _(!0,!1,"stale",p,m);if(jo(p)>0)return _(!0,!1,"work",p,m);return _(!1,!1,"idle",p,m)}function ue(e,t){switch(e.reason){case"terminal":return"open terminal";case"inflight-call":return"in-flight project call";case"grace":return"starting up";case"unknown":return`unknown activity${e.sampleAgeMs!=null?` (last sample ${Math.round(e.sampleAgeMs/1000)}s old)`:" (no sample yet)"}`;case"stale":return`stale activity (last sample ${Math.round((e.sampleAgeMs??0)/1000)}s old)`;case"work":return e.countsText;case"dead":return"process gone";case"idle":return t!=null?`idle ${Math.round(t/60000)}m, ${e.countsText}`:`idle, ${e.countsText}`}}var et=37100,tt=37900,Qt=1,Xo=2,Te=16,zo=900000,Zo=30000,Qo=3000,er=8,en=1000,tn=60000,tr=8,rr=300000,or=3600000,rn=8,on=120000,nn=900000,sn=60000,an=500,ln=30000,nr=25000,cn=5000,un=500,pn=process.env.SHOGO_PERF_LOG==="1";function $e(e,t,r,o){if(!pn)return;console.log(`[shogo-perf] ${JSON.stringify({perf:"open",source:"worker",phase:e,projectId:t,openAttemptId:r,atMs:Date.now(),...o})}`)}var dn=(e)=>({command:e,args:[]}),rt=null;function ur(){if(!rt)rt=Ho(32).toString("hex");return rt}function ir(e){return lr("sha256",ur()).update(`runtime:${e}`).digest("hex")}function mn(e){return lr("sha256",ur()).update(`webhook:${e}`).digest("hex")}function sr(e){let t=e.indexOf("?");if(t===-1)return{pathname:e,search:""};return{pathname:e.slice(0,t),search:e.slice(t)}}function ar(e){try{return cr(e).length===0}catch{return!0}}function ot(e,t,r){let o=[];switch(o.push(`Cannot spawn agent-runtime for project ${e}: no workspace directory available.`),o.push(""),t){case"no-auto-pull-config":o.push(" Reason: WorkerRuntimeManager was constructed without an `autoPull` config and no caller-provided `projectDir` was found on disk."),o.push(" This usually means a programmatic embedder forgot to wire up enrichSpawnConfig "+"or autoPull. CLI users should not see this — please file a bug.");break;case"no-projects-dir":o.push(" Reason: auto-pull is configured but `projectsDir` is empty. The worker needs a persistent root directory under which it can store cloned project workspaces.");break;case"auto-pull-disabled":o.push(` Reason: auto-pull was disabled (--no-auto-pull) and the expected pre-pulled workspace at ${r} is missing or empty.`);break}return o.push(""),o.push(" How to fix (pick one):"),o.push(" 1. Re-enable auto-pull (default). Drop the --no-auto-pull flag and restart"),o.push(" the worker. The first inbound request for this project will clone its"),o.push(" workspace from Shogo Cloud into <projectsDir>/<projectId>/."),o.push(""),o.push(" 2. Pre-pull manually with `shogo project pull <projectId>` before starting"),o.push(" the worker. Use this when you want full control over when the clone runs"),o.push(" (slow links, scheduled maintenance windows, etc.)."),o.push(""),o.push(" 3. Point the worker at an existing workspace by setting either:"),o.push(" --projects-dir <path> (per-invocation flag)"),o.push(" SHOGO_PROJECTS_DIR=<path> (env var, persists across restarts)"),o.push(" shogo config set projectsDir <path>"),o.push(" Whichever path you pick must contain a subdirectory named after the "),o.push(` project id (e.g. <path>/${e}/) populated with the project's source.`),o.push(""),o.push(" Docs: https://shogo.ai/docs/self-hosted-worker#workspace-seeding"),o.join(`
|
|
11
|
-
`)}class nt{opts;log;runtimes=new Map;usedPorts=new Set;spawnCommand;resolved=null;stopped=!1;resolvedLimits=void 0;watchers=new Map;pulledProjects=new Set;syncModes=new Map;activityTimer=null;hostLimitsFile=null;hostLimitsWatcher=null;hostLimitsPolling=!1;constructor(e={}){this.opts=e,this.log=e.logger??console,this.spawnCommand=e.spawnCommand??dn,this.sweepOrphanedViteWatches(),this.ensureActivitySampler(),this.watchHostLimits()}resolveBinary(){if(!this.resolved)this.resolved=this.opts.resolveBin?this.opts.resolveBin():K({flag:this.opts.runtimeBin,env:this.opts.env});return this.resolved}async resolveLocalUrl(e,t){let{pathname:r,search:o}=sr(e);if(!(r.startsWith("/agent/")||r==="/agent"))return null;if(!t){let s=this.getActiveProjects();if(s.length!==1)return null;t=s[0]}let n=await this.spawnConfigFor(t);if(!n)return this.log.warn(`[WorkerRuntimeManager] No spawn config for ${t} — set defaultSpawnConfig or enrichSpawnConfig`),null;let i=await this.ensureRunning(t,n);if(!i.agentPort)return null;return this.touch(t),`http://127.0.0.1:${i.agentPort}${r}${o}`}deriveRuntimeToken(e){return ir(e)}describeRejection(e,t){let{pathname:r}=sr(e);if(!(r.startsWith("/agent/")||r==="/agent"))return{code:"CLI_WORKER_HAS_NO_DATA_API",message:`cli-worker only serves /agent/* paths; tried: ${r}`};return{code:"CLI_WORKER_NO_PROJECT_FOR_PATH",message:`cli-worker received an /agent path without a single active project; projectId=${t??"none"}, path=${r}`}}async spawnConfigFor(e){let t=this.opts.defaultSpawnConfig;if(!t)return null;if(this.opts.enrichSpawnConfig)try{return await this.opts.enrichSpawnConfig(e,t)}catch(r){this.log.warn(`[WorkerRuntimeManager] enrichSpawnConfig failed for ${e}: ${r?.message??r}`)}return t}async ensureRunning(e,t){if(this.stopped)throw Error("WorkerRuntimeManager is stopped");let r=this.runtimes.get(e);if(r?.status==="failed"){let i=this.breakerCooldownMs(r.breakerTrips),s=Date.now()-r.lastFailureAt;if(s>=i)this.log.log(`[WorkerRuntimeManager] auto-heal: ${e} cooldown elapsed (${Math.round(s/1000)}s since trip #${r.breakerTrips}) — `+"clearing circuit breaker and attempting a fresh spawn"),this.healBreaker(r);else{let a=i-s;throw Error(`[WorkerRuntimeManager] cannot ensureRunning(${e}): ${r.lastError??"runtime is in failed state"} Will auto-retry in ~${Math.ceil(a/1000)}s; call resetFailure(${e}) to retry immediately, or stop(${e}) to give up.`)}}t=await this.maybeAutoPull(e,t);let o=this.runtimes.get(e);if(o?.status==="running")return this.touch(e),this.snapshot(o);if(o?.startPromise){let i=await o.startPromise;return this.snapshot(i)}let n=o??this.makeSlot(e,t);if(!o)this.runtimes.set(e,n);n.spawnConfig=t,n.startPromise=this.doStart(n);try{let i=await n.startPromise;return this.enforceMaxRuntimes(e),this.snapshot(i)}finally{n.startPromise=null}}enforceMaxRuntimes(e){let t=this.opts.maxRuntimes;if(t==null||!Number.isFinite(t)||t<=0)return;let r=Date.now(),o=Array.from(this.runtimes.values()).filter((i)=>i.status==="running");if(o.length<=t)return;let n=o.filter((i)=>i.projectId!==e&&r-i.lastUsedAt>=Zo).sort((i,s)=>i.lastUsedAt-s.lastUsedAt);this.evictLru(t,o.length,n,r)}async evictLru(e,t,r,o){let n=t-e,i=new Map;await Promise.all(r.map(async(s)=>{i.set(s.projectId,await this.freshActivityDecision(s.projectId))}));for(let s of r){if(n<=0)break;if(s.status!=="running"||this.runtimes.get(s.projectId)!==s)continue;let a=i.get(s.projectId);if(!a)continue;if(a.dead){this.log.log(`[WorkerRuntimeManager] maxRuntimes=${e}: reaping dead ${s.projectId} (${ue(a)})`),this.stop(s.projectId,"SIGTERM","dead").catch((c)=>{this.log.warn(`[WorkerRuntimeManager] reap of ${s.projectId} failed: ${c?.message??c}`)}),n--;continue}if(a.busy){this.log.log(`[WorkerRuntimeManager] maxRuntimes=${e}: keeping ${s.projectId} over cap=${e}: ${a.reason} (${ue(a)})`);continue}let l=o-s.lastUsedAt;this.log.log(`[WorkerRuntimeManager] maxRuntimes=${e} exceeded (${t} running) — `+`LRU-evicting ${s.projectId} (idle ${Math.round(l/1000)}s, ${a.countsText})`),this.stop(s.projectId,"SIGTERM","lru-evict").catch((c)=>{this.log.warn(`[WorkerRuntimeManager] maxRuntimes eviction of ${s.projectId} failed: ${c?.message??c}`)}),n--}if(n>0)this.log.log(`[WorkerRuntimeManager] maxRuntimes=${e} still exceeded by ${n} after eviction pass — `+"remaining over-cap slots are active or unverifiable; will retry on next spawn / idle reap")}async ensurePulled(e,t){return this.maybeAutoPull(e,t)}async maybeAutoPull(e,t){let r=this.opts.autoPull;if(t.projectDir&&Ze(t.projectDir))return t;if(!r)throw Error(ot(e,"no-auto-pull-config",null));if(!r.projectsDir)throw Error(ot(e,"no-projects-dir",null));if(!r.enabled){let s=Y(r.projectsDir,e);if(Ze(s)&&!ar(s))return{...t,projectDir:s};throw Error(ot(e,"auto-pull-disabled",s))}if(this.pulledProjects.has(e))return{...t,projectDir:Y(r.projectsDir,e)};let o=Y(r.projectsDir,e),n=r.logger??this.log,i={cloneProject:r.gitOps?.cloneProject??Jt,gitIsAvailable:r.gitOps?.gitIsAvailable??Kt,isGitRepo:r.gitOps?.isGitRepo??Re};this.pulledProjects.add(e);try{Yt(o,{recursive:!0});let s=ar(o),a=i.isGitRepo(o),u=(r.useGit!==!1?await i.gitIsAvailable():!1)&&(s||a)?"git":"files";if(this.syncModes.set(e,u),u==="git"){if(s){n.log(`[WorkerRuntimeManager] auto-pull: git clone project ${e} into ${o}`);try{let p=await i.cloneProject({apiUrl:t.cloudUrl,apiKey:t.apiKey,projectId:e,localDir:o,shallow:!0,logger:n});n.log(`[WorkerRuntimeManager] auto-pull: ${e} cloned at ${p.commitSha.slice(0,8)}`)}catch(p){n.warn(`[WorkerRuntimeManager] auto-pull: git clone failed for ${e} (${p?.message??p}); falling back to CloudFileTransport.downloadAll`),this.syncModes.set(e,"files"),await this.fileTransportClone(e,o,t,n)}}else if(a)n.log(`[WorkerRuntimeManager] auto-pull: ${e} already has .git/; skipping clone`);if(this.syncModes.get(e)==="git")await this.topUpShogoState(e,o,t,n)}else if(s)await this.fileTransportClone(e,o,t,n);else n.log(`[WorkerRuntimeManager] auto-pull: ${e} workspace already populated; skipping clone`);if(r.watch!==!1&&!this.watchers.has(e))try{let p=new Qe({apiUrl:t.cloudUrl,apiKey:t.apiKey,projectId:e,localDir:o}),m=this.syncModes.get(e)??"files",f=new ce({rootDir:o,transport:p,logger:n,mode:m,git:m==="git"?{apiUrl:t.cloudUrl,apiKey:t.apiKey,projectId:e}:void 0});f.start(),this.watchers.set(e,f)}catch(p){n.warn(`[WorkerRuntimeManager] auto-pull: watcher start failed for ${e}: ${p?.message??p}`)}}catch(s){n.warn(`[WorkerRuntimeManager] auto-pull: failed for ${e} — runtime will fall back to template defaults. `+`(${s?.message??s})`)}return{...t,projectDir:o}}async fileTransportClone(e,t,r,o){o.log(`[WorkerRuntimeManager] auto-pull: file-transport clone of ${e} into ${t}`);let i=await new Qe({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:t}).downloadAll();o.log(`[WorkerRuntimeManager] auto-pull: ${e} downloaded ${i.downloaded} files (${i.errors.length} errors)`)}async topUpShogoState(e,t,r,o){try{let n=new Qe({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:t}),s=(await n.listManifest()).filter((l)=>l.path===".shogo"||l.path.startsWith(".shogo/"));if(s.length===0)return;let a=await n.downloadFiles(s);o.log(`[WorkerRuntimeManager] auto-pull: ${e} .shogo/ top-up downloaded ${a.downloaded} files (${a.errors.length} errors)`)}catch(n){o.warn(`[WorkerRuntimeManager] auto-pull: .shogo top-up failed for ${e}: ${n?.message??n}`)}}shouldDeferToCloudSync(e){return!!this.opts.autoPull?.enabled&&this.pulledProjects.has(e)}status(e){let t=this.runtimes.get(e);return t?this.snapshot(t):null}getActiveProjects(){return Array.from(this.runtimes.keys()).filter((e)=>{let t=this.runtimes.get(e);return t&&(t.status==="running"||t.status==="starting"||t.status==="restarting")})}touch(e){let t=this.runtimes.get(e);if(!t)return;t.lastUsedAt=Date.now(),this.armIdleTimer(t)}async stop(e,t="SIGTERM",r="stop"){let o=this.runtimes.get(e);if(!o)return;if(o.status="stopping",o.restartTimer)clearTimeout(o.restartTimer),o.restartTimer=null;if(o.idleTimer)clearTimeout(o.idleTimer),o.idleTimer=null;if(o.graceTimer)clearTimeout(o.graceTimer),o.graceTimer=null;if(o.limitWatchdog)o.limitWatchdog.stop(),o.limitWatchdog=null;if(o.proc){this.killProcessGroup(o,t),this.killViteWatch(o);try{o.proc.kill(t)}catch{}await this.waitForExit(o.proc,5000),this.killProcessGroup(o,"SIGKILL"),this.killViteWatch(o),this.reapChildLedgerFor(e,o.spawnConfig.projectDir)}o.pid=null,this.releasePort(o.agentPort),this.runtimes.delete(e),this.notifyGone(e,{reason:r})}notifyGone(e,t){let r=this.opts.onRuntimeGone;if(!r)return;try{r(e,t)}catch(o){this.log.warn(`[WorkerRuntimeManager] onRuntimeGone(${e}) threw: ${o?.message??o}`)}}resetFailure(e){let t=this.runtimes.get(e);if(!t||t.status!=="failed")return!1;if(t.restartTimer)clearTimeout(t.restartTimer),t.restartTimer=null;if(t.idleTimer)clearTimeout(t.idleTimer),t.idleTimer=null;if(t.graceTimer)clearTimeout(t.graceTimer),t.graceTimer=null;if(t.limitWatchdog)t.limitWatchdog.stop(),t.limitWatchdog=null;return this.runtimes.delete(e),this.log.log(`[WorkerRuntimeManager] resetFailure: ${e} cleared, next ensureRunning will respawn`),!0}breakerCooldownMs(e){let t=Math.max(0,e-1),r=on*Math.pow(2,t);return Math.min(r,nn)}healBreaker(e){e.status="stopped",e.consecutiveFailures=0}async stopAll(e="SIGTERM"){if(this.stopped=!0,this.unwatchHostLimits(),this.activityTimer)clearInterval(this.activityTimer),this.activityTimer=null;let t=Array.from(this.watchers.keys());await Promise.all(t.map(async(o)=>{let n=this.watchers.get(o);if(this.watchers.delete(o),n)try{await n.stop()}catch(i){this.log.warn(`[WorkerRuntimeManager] watcher stop ${o}: ${i?.message??i}`)}}));let r=Array.from(this.runtimes.keys());await Promise.all(r.map((o)=>this.stop(o,e).catch((n)=>{this.log.error(`[WorkerRuntimeManager] Failed to stop ${o}: ${n?.message??n}`)})))}makeSlot(e,t){return{projectId:e,agentPort:0,apiServerPort:0,status:"starting",proc:null,pid:null,startedAt:0,lastStdoutAt:0,lastUsedAt:Date.now(),restarts:0,consecutiveFailures:0,lastFailureAt:0,breakerTrips:0,restartTimestamps:[],graceTimer:null,restartTimer:null,idleTimer:null,limitWatchdog:null,spawnConfig:t,startPromise:null}}async doStart(e){let t=this.resolveBinary();if(!t)throw e.status="error",e.lastError="agent-runtime binary not found (run `shogo runtime install`)",Error(e.lastError);if(!e.agentPort)e.agentPort=await this.allocatePort(),e.apiServerPort=e.agentPort+Qt;let r=this.buildEnv(e,t.path);if(e.runtimeToken=r.RUNTIME_AUTH_SECRET,r.SHOGO_EXEC_PIDS_FILE)try{Bo(r.SHOGO_EXEC_PIDS_FILE,{force:!0})}catch{}let o=this.resolveCwd(e),n=this.spawnCommand(t.path),i=this.resolveLimits(),{command:s,args:a}=n,l=!1,c=null;if(i){let g=`shogo-rt-${e.projectId.slice(0,8)}-${Date.now().toString(36)}`,d=Ft({command:n.command,args:n.args,limits:i,scopeName:g,logger:this.log});s=d.command,a=d.args,l=d.wrapped,c=d.wrapped?g:null}e.cgroupScope=c,this.log.log(`[WorkerRuntimeManager] Spawning agent-runtime for ${e.projectId} via ${s} ${a.join(" ")} (port=${e.agentPort}, source=${t.source}${i?`, memCeil=${i.memoryMB}M`:""})`);let u=process.platform!=="win32",p=T(s,a,{cwd:o,env:r,detached:u,stdio:["ignore","pipe","pipe"]});if(u)try{p.unref()}catch{}e.proc=p,e.pid=p.pid??null,e.status="starting",e.startedAt=Date.now(),e.activity=null,e.stderrTail=[];let m=e.spawnConfig.extraEnv?.SHOGO_OPEN_ID;if($e("agent-spawned",e.projectId,m,{pid:e.pid,command:s,args:a,source:t.source}),i)try{this.applyPostSpawnLimits(e,i,l)}catch(g){this.log.warn(`[WorkerRuntimeManager] failed to apply resource limits for ${e.projectId}: ${g?.message??g}`)}e.lastStdoutAt=e.startedAt,p.on("error",(g)=>{e.lastError=g?.message??String(g),this.log.error(`[WorkerRuntimeManager] spawn error for ${e.projectId}: ${e.lastError}`)}),p.on("exit",(g,d)=>{this.handleExit(e,g,d)});let f=`[runtime:${e.projectId.slice(0,8)}]`,y=!0;p.stdout?.on("data",(g)=>{if(e.lastStdoutAt=Date.now(),y)y=!1,$e("agent-first-output",e.projectId,m,{elapsedMs:Date.now()-(e.startedAt??Date.now())});for(let d of g.toString().trimEnd().split(`
|
|
12
|
-
`))if(d)this.log.log(`${f} ${d}`)}),
|
|
13
|
-
`)){if(!v)continue;if(this.log.error(`${f} ${v}`),d.push(v),d.length>
|
|
9
|
+
`));return!t}import{accessSync as mo,constants as ho,existsSync as go}from"node:fs";import{delimiter as fo,join as yo}from"node:path";function K(e={}){let t=e.env??process.env,r=wo(e);for(let i of r)if(Dt(i.path))return i;let o=e.systemBinName??Ot(),n=vo(o,t.PATH);if(n)return{path:n,source:"path"};return null}function wo(e){let t=e.env??process.env,r=[];if(e.flag&&e.flag.trim())r.push({path:e.flag.trim(),source:"flag"});let o=t.SHOGO_AGENT_RUNTIME_BIN?.trim();if(o)r.push({path:o,source:"env"});return r.push({path:O,source:"home"}),r}function Ot(){return process.platform==="win32"?"shogo-agent-runtime.exe":"shogo-agent-runtime"}function Dt(e){if(!go(e))return!1;try{if(process.platform==="win32")return!0;return mo(e,ho.X_OK),!0}catch{return!1}}function vo(e,t){if(!t)return null;for(let r of t.split(fo)){if(!r)continue;let o=yo(r,e);if(Dt(o))return o}return null}function Y(e={}){let t=e.env??process.env,r=[];if(r.push("Error: agent-runtime binary not found."),r.push(""),r.push("Looked in (priority order):"),e.flag)r.push(` --runtime-bin ${e.flag}`);if(t.SHOGO_AGENT_RUNTIME_BIN)r.push(` $SHOGO_AGENT_RUNTIME_BIN = ${t.SHOGO_AGENT_RUNTIME_BIN}`);return r.push(` ${O} (default install location)`),r.push(` ${Ot()} on $PATH`),r.push(""),r.push("Fix:"),r.push(" - Run `shogo runtime install` to download the latest binary, or"),r.push(" - Pass `--runtime-bin <path>` to point at an existing build."),r.join(`
|
|
10
|
+
`)}W();import{createHmac as cr,randomBytes as Vo}from"node:crypto";import{existsSync as et,mkdirSync as Xt,readdirSync as ur,rmSync as qo,unwatchFile as Yo,watch as Xo,watchFile as zo}from"node:fs";import{createConnection as Zo}from"node:net";import{tmpdir as zt}from"node:os";import{basename as Qo,dirname as Zt,join as X}from"node:path";W();import{existsSync as Ut,mkdirSync as bo,readFileSync as Ft,writeFileSync as ae}from"node:fs";import{join as U}from"node:path";var Wt=1e5;function So(e){try{let t=JSON.parse(Ft(e,"utf-8"));return t&&typeof t==="object"?t:null}catch{return null}}function Po(e){let t=parseInt(e.SHOGO_EXEC_MEMORY_MB||"",10);if(Number.isFinite(t)&&t>0)return t;if(!e.SHOGO_HOST_LIMITS_FILE)return 0;let r=So(e.SHOGO_HOST_LIMITS_FILE)?.execMemoryMB;return typeof r==="number"&&Number.isFinite(r)&&r>0?Math.floor(r):0}function je(e){let t=parseInt(e.RUNTIME_MEMORY_MB||"",10);if(!Number.isFinite(t)||t<=0)return null;let r=parseInt(e.RUNTIME_CPU_PERCENT||"0",10),o=Number.isFinite(r)&&r>0?r:0,n=parseInt(e.RUNTIME_TASKS_MAX||"",10),i=Number.isFinite(n)&&n>0?n:4096;return{memoryMB:t,osMemoryMB:t+Po(e),cpuPercent:o,tasksMax:i}}function Lt(e,t){let o=`--max-old-space-size=${Math.max(256,Math.floor(t*0.75))}`,n=e.NODE_OPTIONS?.trim();if(n){if(/--max-old-space-size=/.test(n))return;e.NODE_OPTIONS=`${n} ${o}`}else e.NODE_OPTIONS=o}function ko(){if(process.platform!=="linux")return!1;try{return we("systemd-run",["--version"],{stdio:"ignore",timeout:3000}).status===0}catch{return!1}}function Nt(e){let{command:t,args:r,limits:o,scopeName:n,logger:i}=e;if(process.platform!=="linux"||!ko())return{command:t,args:r,wrapped:!1};let s=["-p",`MemoryMax=${o.osMemoryMB}M`,"-p",`MemoryHigh=${Le(o.osMemoryMB)}M`,"-p",`TasksMax=${o.tasksMax}`];if(o.cpuPercent>0)s.push("-p",`CPUQuota=${o.cpuPercent}%`);let a=["--user","--scope","--quiet","--collect",`--unit=${n}`,...s,"--",t,...r];return i?.log(`[resource-limits] wrapping spawn in systemd scope ${n} (MemoryMax=${o.osMemoryMB}M)`),{command:"systemd-run",args:a,wrapped:!0}}function Le(e){return Math.max(128,Math.floor(e*0.9))}function jt(e){return U("/sys/fs/cgroup","shogo",`rt-${e}`)}function Ht(e){let{pid:t,limits:r,logger:o}=e;if(process.platform!=="linux")return!1;if(!Ut(U("/sys/fs/cgroup","cgroup.controllers")))return!1;let i=jt(t);try{if(bo(i,{recursive:!0}),ae(U(i,"memory.max"),String(r.osMemoryMB*1024*1024)),r.cpuPercent>0){let s=Math.floor(r.cpuPercent/100*Wt);ae(U(i,"cpu.max"),`${s} ${Wt}`)}try{ae(U(i,"pids.max"),String(r.tasksMax))}catch{}return ae(U(i,"cgroup.procs"),String(t)),o?.log(`[resource-limits] attached pid ${t} to cgroup ${i} (memory.max=${r.osMemoryMB}M)`),!0}catch(s){return o?.warn(`[resource-limits] direct cgroup attach failed for pid ${t}: ${s?.message??s}`),!1}}function Bt(e,t={}){let r=t.platform??process.platform,{limits:o,logger:n}=e;if(r==="win32")return n?.log("[resource-limits] Job Object memory limit applies on the next runtime spawn"),!1;if(r!=="linux")return!1;if(e.scopeName){let l=["--user","set-property","--runtime",e.scopeName,`MemoryMax=${o.osMemoryMB}M`,`MemoryHigh=${Le(o.osMemoryMB)}M`];if(t.run)return t.run("systemctl",l),!0;return q("systemctl",l,{timeout:5000},(c)=>{if(c)n?.warn(`[resource-limits] set-property ${e.scopeName} failed: ${c.message}`);else n?.log(`[resource-limits] ${e.scopeName} MemoryMax=${o.osMemoryMB}M`)}),!0}if(!e.pid)return!1;let i=jt(e.pid),s=t.exists??Ut,a=t.writeFile??ae;try{if(!s(U(i,"memory.max")))return!1;if(a(U(i,"memory.max"),String(o.osMemoryMB*1024*1024)),s(U(i,"memory.high")))a(U(i,"memory.high"),String(Le(o.osMemoryMB)*1024*1024));return n?.log(`[resource-limits] raised cgroup ${i} memory.max to ${o.osMemoryMB}M`),!0}catch(l){return n?.warn(`[resource-limits] live cgroup update failed for ${i}: ${l?.message??l}`),!1}}function Gt(e){let{pid:t,limits:r,logger:o}=e;if(process.platform!=="win32")return!1;let n=process.env.SHOGO_WINJOB_HELPER||"@shogo/winjob";try{let i=Ce(n);if(typeof i.limitProcess!=="function")return!1;let s=i.limitProcess(t,{memoryBytes:r.osMemoryMB*1024*1024,killOnClose:!0});if(s)o?.log(`[resource-limits] attached pid ${t} to Job Object (${r.osMemoryMB}M)`);return s}catch{return!1}}function Kt(e){try{let t=JSON.parse(Ft(e,"utf-8"));if(!Array.isArray(t.pids))return[];return t.pids.filter((r)=>Number.isInteger(r)&&r>0)}catch{return[]}}var Ro=20000,To=`$ErrorActionPreference='SilentlyContinue';foreach($p in Get-CimInstance Win32_Process -Property ProcessId,ParentProcessId,WorkingSetSize){"$($p.ProcessId) $($p.ParentProcessId) $([math]::Floor($p.WorkingSetSize/1024))"}`;function $o(e=process.platform){let[t,r]=e==="win32"?["powershell.exe",["-NoProfile","-NonInteractive","-Command",To]]:["ps",["-A","-o","pid=,ppid=,pgid=,rss="]];return new Promise((o)=>{q(t,r,{encoding:"utf-8",timeout:Ro,maxBuffer:8388608},(n,i)=>{if(n||!i){o(null);return}let s=[];for(let a of String(i).split(/\r?\n/)){let l=a.trim().split(/\s+/).map((c)=>parseInt(c,10));if(l.some((c)=>!Number.isFinite(c)))continue;if(e==="win32"&&l.length===3)s.push({pid:l[0],ppid:l[1],pgid:0,kb:l[2]});else if(e!=="win32"&&l.length===4)s.push({pid:l[0],ppid:l[1],pgid:l[2],kb:l[3]})}o(s.length?s:null)})})}function Io(e,t,r=process.platform,o=[]){if(r!=="win32"){let p=e.filter((u)=>u.pgid===t);return p.length?p.reduce((u,m)=>u+m.kb,0):null}if(!e.some((p)=>p.pid===t))return null;let n=new Set(o.filter((p)=>p!==t)),i=new Map;for(let p of e){if(p.pid===p.ppid)continue;let u=i.get(p.ppid);if(u)u.push(p);else i.set(p.ppid,[p])}let s=new Map(e.map((p)=>[p.pid,p])),a=new Set,l=[t],c=0;while(l.length){let p=l.pop();if(a.has(p))continue;a.add(p),c+=s.get(p)?.kb??0;for(let u of i.get(p)??[])if(!n.has(u.pid))l.push(u.pid)}return c}var Ne=new Map;function xo(e,t,r){let o=`${t}:${r}`,n=Ne.get(o);if(!n){let i={key:o,watchdogs:new Set,sampling:!1,timer:setInterval(()=>{Eo(i,t)},r)};try{i.timer.unref?.()}catch{}Ne.set(o,i),n=i}return n.watchdogs.add(e),n}async function Eo(e,t){if(e.sampling)return;e.sampling=!0;try{let r=await $o(t);for(let o of[...e.watchdogs])o.observe(r)}finally{e.sampling=!1}}class He{sampler=null;consecutive=0;fired=!1;pid;ceilingMB;platform;intervalMs;strikes;excludePids;logger;onBreach;constructor(e){this.pid=e.pid,this.ceilingMB=e.ceilingMB,this.platform=e.platform??process.platform,this.intervalMs=e.intervalMs??parseInt(process.env.RUNTIME_RSS_WATCHDOG_MS||"15000",10),this.strikes=e.strikes??parseInt(process.env.RUNTIME_RSS_WATCHDOG_STRIKES||"2",10),this.excludePids=e.excludePids,this.logger=e.logger,this.onBreach=e.onBreach}start(){if(this.sampler)return;this.sampler=xo(this,this.platform,Math.max(2000,this.intervalMs))}stop(){if(this.sampler?.watchdogs.delete(this),this.sampler?.watchdogs.size===0)clearInterval(this.sampler.timer),Ne.delete(this.sampler.key);this.sampler=null}observe(e){if(this.fired)return;let t=e?Io(e,this.pid,this.platform,this.excludePids?.()??[]):null;if(t==null){this.consecutive=0;return}let r=Math.floor(t/1024);if(r<=this.ceilingMB){this.consecutive=0;return}if(this.consecutive+=1,this.logger?.warn(`[resource-limits] pid ${this.pid} RSS ${r}MB over ceiling ${this.ceilingMB}MB (strike ${this.consecutive}/${this.strikes})`),this.consecutive>=this.strikes)this.fired=!0,this.stop(),this.onBreach(r)}}import{CloudFileTransport as tt}from"@shogo-ai/sdk/cloud-file-transport";import{killViteWatchFromPidfile as Qt}from"@shogo-ai/sdk/vite-watch";import{reapChildLedger as en}from"@shogo-ai/sdk/child-ledger";import{watch as qt,statSync as Oo}from"node:fs";import{relative as Ss,sep as Do,posix as Wo}from"node:path";var Uo=1500,Fo=new Set(["node_modules",".git","dist","build",".vite",".cache"]),Lo=[".shogo/"];function No(e){if(!e)return!0;return e.split("/").some((t)=>Fo.has(t))}class ce{rootDir;transport;debounceMs;logger;onFlush;mode;git;commitAndPush;watcher=null;pending=new Set;timer=null;flushing=!1;stopped=!1;constructor(e){if(this.rootDir=e.rootDir,this.transport=e.transport,this.debounceMs=e.debounceMs??Uo,this.logger=e.logger??console,this.onFlush=e.onFlush,this.mode=e.mode??"files",this.mode==="git"&&!e.git)throw Error('CloudSyncWatcher: mode: "git" requires the `git` option block');this.git=e.git,this.commitAndPush=e.commitAndPush??(async(t)=>{let r=await Promise.resolve().then(() => (Re(),{}));return Co(t)})}start(){if(this.watcher)return;if(this.stopped)throw Error("CloudSyncWatcher: already stopped, build a fresh instance");try{this.watcher=qt(this.rootDir,{recursive:!0},(e,t)=>{if(!t)return;this.handleEvent(String(t))})}catch(e){this.logger.warn(`[CloudSyncWatcher] recursive watch failed (${e?.message??e}); falling back to root-only watch.`),this.watcher=qt(this.rootDir,(t,r)=>{if(!r)return;this.handleEvent(String(r))})}}async stop(){if(this.stopped)return;if(this.stopped=!0,this.timer)clearTimeout(this.timer),this.timer=null;if(this.watcher){try{this.watcher.close()}catch{}this.watcher=null}if(this.pending.size>0)await this.flush()}handleEvent(e){if(this.stopped)return;let t=e.split(Do).join(Wo.sep);if(No(t))return;try{let r=`${this.rootDir}/${t}`;if(Oo(r).isDirectory())return}catch{return}this.pending.add(t),this.scheduleFlush()}scheduleFlush(){if(this.timer)return;this.timer=setTimeout(()=>{this.timer=null,this.flush()},this.debounceMs)}async flush(){if(this.flushing){this.scheduleFlush();return}if(this.pending.size===0)return;this.flushing=!0;let e=Array.from(this.pending);this.pending.clear();try{if(this.mode==="git")await this.flushGit(e);else await this.flushFiles(e)}catch(t){this.logger.error(`[CloudSyncWatcher] flush failed: ${t?.message??t}`);for(let r of e)this.pending.add(r);this.scheduleFlush()}finally{this.flushing=!1}}async flushFiles(e){if(e.length===0)return;let t=await this.transport.uploadFiles(e);if(this.onFlush?.({uploaded:e,errors:t.errors.length}),t.errors.length>0)for(let r of t.errors)this.logger.warn(`[CloudSyncWatcher] upload ${r.path}: ${r.message}`)}async flushGit(e){if(!this.git)throw Error("CloudSyncWatcher: missing git options");let t=[],r=[];for(let i of e)if(Lo.some((s)=>i===s.slice(0,-1)||i.startsWith(s)))t.push(i);else r.push(i);if(t.length>0){let i=await this.transport.uploadFiles(t);if(i.errors.length>0)for(let s of i.errors)this.logger.warn(`[CloudSyncWatcher] upload ${s.path}: ${s.message}`)}let o=!1,n;if(r.length>0){let i=`auto: ${new Date().toISOString()}`;try{let s=await this.commitAndPush({apiUrl:this.git.apiUrl,apiKey:this.git.apiKey,projectId:this.git.projectId,localDir:this.rootDir,message:i,branch:this.git.branch,logger:this.logger});o=s.committed,n=s.commitSha}catch(s){throw s}}this.onFlush?.({uploaded:e,errors:0,committed:o,commitSha:n})}}Re();var Yt=1e4,qe=2000;var Ye={turns:0,tasks:0,streams:0,children:0};function jo(e){return`turns=${e.turns} tasks=${e.tasks} streams=${e.streams} children=${e.children}`}function Ho(e){return e.turns+e.tasks+e.streams+e.children}function A(e,t,r,o,n){return{busy:e,dead:t,reason:r,counts:o,countsText:jo(o),...n!=null?{sampleAgeMs:n}:{}}}function Xe(e){let{now:t,startedAt:r,status:o,sample:n,processExited:i=!1,startupGraceMs:s=120000,staleMs:a=30000,deadAfterMs:l=60000}=e,c=e.terminalSessions??0,p=e.inflightCalls??0,u=n?.counts??Ye,m=n?Math.max(0,t-n.at):void 0;if(c>0)return A(!0,!1,"terminal",u,m);if(p>0)return A(!0,!1,"inflight-call",u,m);if(i)return A(!1,!0,"dead",u,m);if(o==="starting"||o==="restarting")return A(!0,!1,"grace",u,m);let f=t-(r||t);if(f>=0&&f<s)return A(!0,!1,"grace",u,m);if(o!=="running")return A(!0,!1,"unknown",u,m);if(!n)return A(!0,!1,"unknown",u,m);if(!n.ok){if((n.failingSince!=null?t-n.failingSince:0)>=l)return A(!1,!0,"dead",u,m);return A(!0,!1,"unknown",u,m)}if((m??0)>a)return A(!0,!1,"stale",u,m);if(Ho(u)>0)return A(!0,!1,"work",u,m);return A(!1,!1,"idle",u,m)}function ue(e,t){switch(e.reason){case"terminal":return"open terminal";case"inflight-call":return"in-flight project call";case"grace":return"starting up";case"unknown":return`unknown activity${e.sampleAgeMs!=null?` (last sample ${Math.round(e.sampleAgeMs/1000)}s old)`:" (no sample yet)"}`;case"stale":return`stale activity (last sample ${Math.round((e.sampleAgeMs??0)/1000)}s old)`;case"work":return e.countsText;case"dead":return"process gone";case"idle":return t!=null?`idle ${Math.round(t/60000)}m, ${e.countsText}`:`idle, ${e.countsText}`}}import{connect as Bo}from"node:net";var ze=37100,Ze=37900,Go=500,Ko=50;async function Jo(e,t={}){let r=t.timeoutMs??Go,o=t.host??"127.0.0.1",n=t.connect??((i)=>Bo(i));return await new Promise((i)=>{let s=!1,a=(c)=>{if(s)return;s=!0;try{l.destroy()}catch{}i(c)},l=n({host:o,port:e});l.setTimeout(r),l.once("connect",()=>a("in-use")),l.once("error",()=>a("free")),l.once("timeout",()=>a("in-use"))})}class Qe{reserved=new Set;probe;random;constructor(e={}){this.probe=e.probe??((t)=>Jo(t,{timeoutMs:e.probeTimeoutMs})),this.random=e.random??Math.random}reserve(e){for(let t of e)if(t>0)this.reserved.add(t)}release(e){for(let t of e)this.reserved.delete(t)}releaseBlock(e,t){if(!e)return;for(let r=0;r<t;r++)this.reserved.delete(e+r)}isReserved(e){return this.reserved.has(e)}async isPortAvailable(e){if(e<=0)return!1;if(this.reserved.has(e))return!1;return await this.probeFree(e)}async allocate(e={}){let t=e.start??ze,r=e.end??Ze,o=e.blockSize??1,n=e.probeOffsets??[0],i=r-t-o+2;if(i<1)throw Error(`Port range ${t}-${r} cannot hold a ${o}-port block`);let s=e.maxAttempts??Math.min(i,Ko);for(let a=0;a<s;a++){let l=t+Math.floor(this.random()*i),c=this.candidatePorts(l,o,n);if(c.some((u)=>this.reserved.has(u)))continue;this.reserve(c);let p=!0;for(let u of n)if(!await this.probeFree(l+u)){p=!1;break}if(!p){this.release(c);continue}return l}throw Error(`Cannot allocate port after ${s} attempts in range ${t}-${r}`)}candidatePorts(e,t,r){let o=new Set;for(let n=0;n<t;n++)o.add(e+n);for(let n of r)o.add(e+n);return[...o].filter((n)=>n>0)}async probeFree(e){if(e<=0)return!1;return await this.probe(e)==="free"}}var tn=ze,rn=Ze,er=1,on=2,rt=16,nn=900000,sn=30000,an=3000,tr=8,ln=/EADDRINUSE|Is port \d+ in use/,cn=1000,un=60000,rr=8,or=300000,nr=3600000,pn=8,dn=120000,mn=900000,hn=60000,gn=500,fn=30000,ir=25000,yn=5000,wn=500,vn=process.env.SHOGO_PERF_LOG==="1";function Te(e,t,r,o){if(!vn)return;console.log(`[shogo-perf] ${JSON.stringify({perf:"open",source:"worker",phase:e,projectId:t,openAttemptId:r,atMs:Date.now(),...o})}`)}var bn=(e)=>({command:e,args:[]}),ot=null;function pr(){if(!ot)ot=Vo(32).toString("hex");return ot}function sr(e){return cr("sha256",pr()).update(`runtime:${e}`).digest("hex")}function Sn(e){return cr("sha256",pr()).update(`webhook:${e}`).digest("hex")}function ar(e){let t=e.indexOf("?");if(t===-1)return{pathname:e,search:""};return{pathname:e.slice(0,t),search:e.slice(t)}}function lr(e){try{return ur(e).length===0}catch{return!0}}function nt(e,t,r){let o=[];switch(o.push(`Cannot spawn agent-runtime for project ${e}: no workspace directory available.`),o.push(""),t){case"no-auto-pull-config":o.push(" Reason: WorkerRuntimeManager was constructed without an `autoPull` config and no caller-provided `projectDir` was found on disk."),o.push(" This usually means a programmatic embedder forgot to wire up enrichSpawnConfig "+"or autoPull. CLI users should not see this — please file a bug.");break;case"no-projects-dir":o.push(" Reason: auto-pull is configured but `projectsDir` is empty. The worker needs a persistent root directory under which it can store cloned project workspaces.");break;case"auto-pull-disabled":o.push(` Reason: auto-pull was disabled (--no-auto-pull) and the expected pre-pulled workspace at ${r} is missing or empty.`);break}return o.push(""),o.push(" How to fix (pick one):"),o.push(" 1. Re-enable auto-pull (default). Drop the --no-auto-pull flag and restart"),o.push(" the worker. The first inbound request for this project will clone its"),o.push(" workspace from Shogo Cloud into <projectsDir>/<projectId>/."),o.push(""),o.push(" 2. Pre-pull manually with `shogo project pull <projectId>` before starting"),o.push(" the worker. Use this when you want full control over when the clone runs"),o.push(" (slow links, scheduled maintenance windows, etc.)."),o.push(""),o.push(" 3. Point the worker at an existing workspace by setting either:"),o.push(" --projects-dir <path> (per-invocation flag)"),o.push(" SHOGO_PROJECTS_DIR=<path> (env var, persists across restarts)"),o.push(" shogo config set projectsDir <path>"),o.push(" Whichever path you pick must contain a subdirectory named after the "),o.push(` project id (e.g. <path>/${e}/) populated with the project's source.`),o.push(""),o.push(" Docs: https://shogo.ai/docs/self-hosted-worker#workspace-seeding"),o.join(`
|
|
11
|
+
`)}class it{opts;log;runtimes=new Map;ports;get usedPorts(){return this.ports.reserved}spawnCommand;resolved=null;stopped=!1;resolvedLimits=void 0;watchers=new Map;pulledProjects=new Set;syncModes=new Map;activityTimer=null;hostLimitsFile=null;hostLimitsWatcher=null;hostLimitsPolling=!1;constructor(e={}){this.opts=e,this.log=e.logger??console,this.ports=e.ports??new Qe,this.spawnCommand=e.spawnCommand??bn,this.sweepOrphanedViteWatches(),this.ensureActivitySampler(),this.watchHostLimits()}resolveBinary(){if(!this.resolved)this.resolved=this.opts.resolveBin?this.opts.resolveBin():K({flag:this.opts.runtimeBin,env:this.opts.env});return this.resolved}async resolveLocalUrl(e,t){let{pathname:r,search:o}=ar(e);if(!(r.startsWith("/agent/")||r==="/agent"))return null;if(!t){let s=this.getActiveProjects();if(s.length!==1)return null;t=s[0]}let n=await this.spawnConfigFor(t);if(!n)return this.log.warn(`[WorkerRuntimeManager] No spawn config for ${t} — set defaultSpawnConfig or enrichSpawnConfig`),null;let i=await this.ensureRunning(t,n);if(!i.agentPort)return null;return this.touch(t),`http://127.0.0.1:${i.agentPort}${r}${o}`}deriveRuntimeToken(e){return sr(e)}describeRejection(e,t){let{pathname:r}=ar(e);if(!(r.startsWith("/agent/")||r==="/agent"))return{code:"CLI_WORKER_HAS_NO_DATA_API",message:`cli-worker only serves /agent/* paths; tried: ${r}`};return{code:"CLI_WORKER_NO_PROJECT_FOR_PATH",message:`cli-worker received an /agent path without a single active project; projectId=${t??"none"}, path=${r}`}}async spawnConfigFor(e){let t=this.opts.defaultSpawnConfig;if(!t)return null;if(this.opts.enrichSpawnConfig)try{return await this.opts.enrichSpawnConfig(e,t)}catch(r){this.log.warn(`[WorkerRuntimeManager] enrichSpawnConfig failed for ${e}: ${r?.message??r}`)}return t}async ensureRunning(e,t){if(this.stopped)throw Error("WorkerRuntimeManager is stopped");let r=this.runtimes.get(e);if(r?.status==="failed"){let i=this.breakerCooldownMs(r.breakerTrips),s=Date.now()-r.lastFailureAt;if(s>=i)this.log.log(`[WorkerRuntimeManager] auto-heal: ${e} cooldown elapsed (${Math.round(s/1000)}s since trip #${r.breakerTrips}) — `+"clearing circuit breaker and attempting a fresh spawn"),this.healBreaker(r);else{let a=i-s;throw Error(`[WorkerRuntimeManager] cannot ensureRunning(${e}): ${r.lastError??"runtime is in failed state"} Will auto-retry in ~${Math.ceil(a/1000)}s; call resetFailure(${e}) to retry immediately, or stop(${e}) to give up.`)}}t=await this.maybeAutoPull(e,t);let o=this.runtimes.get(e);if(o?.status==="running")return this.touch(e),this.snapshot(o);if(o?.startPromise){let i=await o.startPromise;return this.snapshot(i)}let n=o??this.makeSlot(e,t);if(!o)this.runtimes.set(e,n);n.spawnConfig=t,n.startPromise=this.doStart(n);try{let i=await n.startPromise;return this.enforceMaxRuntimes(e),this.snapshot(i)}finally{n.startPromise=null}}enforceMaxRuntimes(e){let t=this.opts.maxRuntimes;if(t==null||!Number.isFinite(t)||t<=0)return;let r=Date.now(),o=Array.from(this.runtimes.values()).filter((i)=>i.status==="running");if(o.length<=t)return;let n=o.filter((i)=>i.projectId!==e&&r-i.lastUsedAt>=sn).sort((i,s)=>i.lastUsedAt-s.lastUsedAt);this.evictLru(t,o.length,n,r)}async evictLru(e,t,r,o){let n=t-e,i=new Map;await Promise.all(r.map(async(s)=>{i.set(s.projectId,await this.freshActivityDecision(s.projectId))}));for(let s of r){if(n<=0)break;if(s.status!=="running"||this.runtimes.get(s.projectId)!==s)continue;let a=i.get(s.projectId);if(!a)continue;if(a.dead){this.log.log(`[WorkerRuntimeManager] maxRuntimes=${e}: reaping dead ${s.projectId} (${ue(a)})`),this.stop(s.projectId,"SIGTERM","dead").catch((c)=>{this.log.warn(`[WorkerRuntimeManager] reap of ${s.projectId} failed: ${c?.message??c}`)}),n--;continue}if(a.busy){this.log.log(`[WorkerRuntimeManager] maxRuntimes=${e}: keeping ${s.projectId} over cap=${e}: ${a.reason} (${ue(a)})`);continue}let l=o-s.lastUsedAt;this.log.log(`[WorkerRuntimeManager] maxRuntimes=${e} exceeded (${t} running) — `+`LRU-evicting ${s.projectId} (idle ${Math.round(l/1000)}s, ${a.countsText})`),this.stop(s.projectId,"SIGTERM","lru-evict").catch((c)=>{this.log.warn(`[WorkerRuntimeManager] maxRuntimes eviction of ${s.projectId} failed: ${c?.message??c}`)}),n--}if(n>0)this.log.log(`[WorkerRuntimeManager] maxRuntimes=${e} still exceeded by ${n} after eviction pass — `+"remaining over-cap slots are active or unverifiable; will retry on next spawn / idle reap")}async ensurePulled(e,t){return this.maybeAutoPull(e,t)}async maybeAutoPull(e,t){let r=this.opts.autoPull;if(t.projectDir&&et(t.projectDir))return t;if(!r)throw Error(nt(e,"no-auto-pull-config",null));if(!r.projectsDir)throw Error(nt(e,"no-projects-dir",null));if(!r.enabled){let s=X(r.projectsDir,e);if(et(s)&&!lr(s))return{...t,projectDir:s};throw Error(nt(e,"auto-pull-disabled",s))}if(this.pulledProjects.has(e))return{...t,projectDir:X(r.projectsDir,e)};let o=X(r.projectsDir,e),n=r.logger??this.log,i={cloneProject:r.gitOps?.cloneProject??Vt,gitIsAvailable:r.gitOps?.gitIsAvailable??Jt,isGitRepo:r.gitOps?.isGitRepo??ke};this.pulledProjects.add(e);try{Xt(o,{recursive:!0});let s=lr(o),a=i.isGitRepo(o),p=(r.useGit!==!1?await i.gitIsAvailable():!1)&&(s||a)?"git":"files";if(this.syncModes.set(e,p),p==="git"){if(s){n.log(`[WorkerRuntimeManager] auto-pull: git clone project ${e} into ${o}`);try{let u=await i.cloneProject({apiUrl:t.cloudUrl,apiKey:t.apiKey,projectId:e,localDir:o,shallow:!0,logger:n});n.log(`[WorkerRuntimeManager] auto-pull: ${e} cloned at ${u.commitSha.slice(0,8)}`)}catch(u){n.warn(`[WorkerRuntimeManager] auto-pull: git clone failed for ${e} (${u?.message??u}); falling back to CloudFileTransport.downloadAll`),this.syncModes.set(e,"files"),await this.fileTransportClone(e,o,t,n)}}else if(a)n.log(`[WorkerRuntimeManager] auto-pull: ${e} already has .git/; skipping clone`);if(this.syncModes.get(e)==="git")await this.topUpShogoState(e,o,t,n)}else if(s)await this.fileTransportClone(e,o,t,n);else n.log(`[WorkerRuntimeManager] auto-pull: ${e} workspace already populated; skipping clone`);if(r.watch!==!1&&!this.watchers.has(e))try{let u=new tt({apiUrl:t.cloudUrl,apiKey:t.apiKey,projectId:e,localDir:o}),m=this.syncModes.get(e)??"files",f=new ce({rootDir:o,transport:u,logger:n,mode:m,git:m==="git"?{apiUrl:t.cloudUrl,apiKey:t.apiKey,projectId:e}:void 0});f.start(),this.watchers.set(e,f)}catch(u){n.warn(`[WorkerRuntimeManager] auto-pull: watcher start failed for ${e}: ${u?.message??u}`)}}catch(s){n.warn(`[WorkerRuntimeManager] auto-pull: failed for ${e} — runtime will fall back to template defaults. `+`(${s?.message??s})`)}return{...t,projectDir:o}}async fileTransportClone(e,t,r,o){o.log(`[WorkerRuntimeManager] auto-pull: file-transport clone of ${e} into ${t}`);let i=await new tt({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:t}).downloadAll();o.log(`[WorkerRuntimeManager] auto-pull: ${e} downloaded ${i.downloaded} files (${i.errors.length} errors)`)}async topUpShogoState(e,t,r,o){try{let n=new tt({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:t}),s=(await n.listManifest()).filter((l)=>l.path===".shogo"||l.path.startsWith(".shogo/"));if(s.length===0)return;let a=await n.downloadFiles(s);o.log(`[WorkerRuntimeManager] auto-pull: ${e} .shogo/ top-up downloaded ${a.downloaded} files (${a.errors.length} errors)`)}catch(n){o.warn(`[WorkerRuntimeManager] auto-pull: .shogo top-up failed for ${e}: ${n?.message??n}`)}}shouldDeferToCloudSync(e){return!!this.opts.autoPull?.enabled&&this.pulledProjects.has(e)}status(e){let t=this.runtimes.get(e);return t?this.snapshot(t):null}getActiveProjects(){return Array.from(this.runtimes.keys()).filter((e)=>{let t=this.runtimes.get(e);return t&&(t.status==="running"||t.status==="starting"||t.status==="restarting")})}touch(e){let t=this.runtimes.get(e);if(!t)return;t.lastUsedAt=Date.now(),this.armIdleTimer(t)}async stop(e,t="SIGTERM",r="stop"){let o=this.runtimes.get(e);if(!o)return;if(o.status="stopping",o.restartTimer)clearTimeout(o.restartTimer),o.restartTimer=null;if(o.idleTimer)clearTimeout(o.idleTimer),o.idleTimer=null;if(o.graceTimer)clearTimeout(o.graceTimer),o.graceTimer=null;if(o.limitWatchdog)o.limitWatchdog.stop(),o.limitWatchdog=null;if(o.proc){this.killProcessGroup(o,t),this.killViteWatch(o);try{o.proc.kill(t)}catch{}await this.waitForExit(o.proc,5000),this.killProcessGroup(o,"SIGKILL"),this.killViteWatch(o),this.reapChildLedgerFor(e,o.spawnConfig.projectDir)}o.pid=null,this.releasePort(o.agentPort),this.runtimes.delete(e),this.notifyGone(e,{reason:r})}notifyGone(e,t){let r=this.opts.onRuntimeGone;if(!r)return;try{r(e,t)}catch(o){this.log.warn(`[WorkerRuntimeManager] onRuntimeGone(${e}) threw: ${o?.message??o}`)}}resetFailure(e){let t=this.runtimes.get(e);if(!t||t.status!=="failed")return!1;if(t.restartTimer)clearTimeout(t.restartTimer),t.restartTimer=null;if(t.idleTimer)clearTimeout(t.idleTimer),t.idleTimer=null;if(t.graceTimer)clearTimeout(t.graceTimer),t.graceTimer=null;if(t.limitWatchdog)t.limitWatchdog.stop(),t.limitWatchdog=null;return this.runtimes.delete(e),this.log.log(`[WorkerRuntimeManager] resetFailure: ${e} cleared, next ensureRunning will respawn`),!0}breakerCooldownMs(e){let t=Math.max(0,e-1),r=dn*Math.pow(2,t);return Math.min(r,mn)}healBreaker(e){e.status="stopped",e.consecutiveFailures=0}async stopAll(e="SIGTERM"){if(this.stopped=!0,this.unwatchHostLimits(),this.activityTimer)clearInterval(this.activityTimer),this.activityTimer=null;let t=Array.from(this.watchers.keys());await Promise.all(t.map(async(o)=>{let n=this.watchers.get(o);if(this.watchers.delete(o),n)try{await n.stop()}catch(i){this.log.warn(`[WorkerRuntimeManager] watcher stop ${o}: ${i?.message??i}`)}}));let r=Array.from(this.runtimes.keys());await Promise.all(r.map((o)=>this.stop(o,e).catch((n)=>{this.log.error(`[WorkerRuntimeManager] Failed to stop ${o}: ${n?.message??n}`)})))}makeSlot(e,t){return{projectId:e,agentPort:0,apiServerPort:0,status:"starting",proc:null,pid:null,startedAt:0,lastStdoutAt:0,lastUsedAt:Date.now(),restarts:0,consecutiveFailures:0,lastFailureAt:0,breakerTrips:0,restartTimestamps:[],graceTimer:null,restartTimer:null,idleTimer:null,limitWatchdog:null,spawnConfig:t,startPromise:null}}async doStart(e){let t=this.resolveBinary();if(!t)throw e.status="error",e.lastError="agent-runtime binary not found (run `shogo runtime install`)",Error(e.lastError);if(!e.agentPort)e.agentPort=await this.allocatePort(),e.apiServerPort=e.agentPort+er;let r=this.buildEnv(e,t.path);if(e.runtimeToken=r.RUNTIME_AUTH_SECRET,r.SHOGO_EXEC_PIDS_FILE)try{qo(r.SHOGO_EXEC_PIDS_FILE,{force:!0})}catch{}let o=this.resolveCwd(e),n=this.spawnCommand(t.path),i=this.resolveLimits(),{command:s,args:a}=n,l=!1,c=null;if(i){let h=`shogo-rt-${e.projectId.slice(0,8)}-${Date.now().toString(36)}`,d=Nt({command:n.command,args:n.args,limits:i,scopeName:h,logger:this.log});s=d.command,a=d.args,l=d.wrapped,c=d.wrapped?h:null}e.cgroupScope=c,this.log.log(`[WorkerRuntimeManager] Spawning agent-runtime for ${e.projectId} via ${s} ${a.join(" ")} (port=${e.agentPort}, source=${t.source}${i?`, memCeil=${i.memoryMB}M`:""})`);let p=process.platform!=="win32",u=T(s,a,{cwd:o,env:r,detached:p,stdio:["ignore","pipe","pipe"]});if(p)try{u.unref()}catch{}e.proc=u,e.pid=u.pid??null,e.status="starting",e.startedAt=Date.now(),e.activity=null,e.stderrTail=[],e.portConflictDiagnosed=!1;let m=e.spawnConfig.extraEnv?.SHOGO_OPEN_ID;if(Te("agent-spawned",e.projectId,m,{pid:e.pid,command:s,args:a,source:t.source}),i)try{this.applyPostSpawnLimits(e,i,l)}catch(h){this.log.warn(`[WorkerRuntimeManager] failed to apply resource limits for ${e.projectId}: ${h?.message??h}`)}e.lastStdoutAt=e.startedAt,u.on("error",(h)=>{e.lastError=h?.message??String(h),this.log.error(`[WorkerRuntimeManager] spawn error for ${e.projectId}: ${e.lastError}`)}),u.on("exit",(h,d)=>{this.handleExit(e,h,d)});let f=`[runtime:${e.projectId.slice(0,8)}]`,y=!0;u.stdout?.on("data",(h)=>{if(e.lastStdoutAt=Date.now(),y)y=!1,Te("agent-first-output",e.projectId,m,{elapsedMs:Date.now()-(e.startedAt??Date.now())});for(let d of h.toString().trimEnd().split(`
|
|
12
|
+
`))if(d)this.log.log(`${f} ${d}`)}),u.stderr?.on("data",(h)=>{if(e.lastStdoutAt=Date.now(),y)y=!1,Te("agent-first-output",e.projectId,m,{elapsedMs:Date.now()-(e.startedAt??Date.now()),stream:"stderr"});let d=e.stderrTail??=[];for(let v of h.toString().trimEnd().split(`
|
|
13
|
+
`)){if(!v)continue;if(this.log.error(`${f} ${v}`),d.push(v),d.length>tr)d.splice(0,d.length-tr);if(!e.portConflictDiagnosed&&ln.test(v))e.portConflictDiagnosed=!0,this.logPortHolder(e)}});try{return await this.waitForHealth(e,fn),Te("agent-health-ready",e.projectId,m,{elapsedMs:Date.now()-(e.startedAt??Date.now()),agentPort:e.agentPort}),e.status="running",e.lastUsedAt=Date.now(),this.armIdleTimer(e),this.armGraceTimer(e),this.sampleActivity(e).catch(()=>null),e}catch(h){e.status="error",e.lastError=h?.message??String(h),this.killProcessGroup(e,"SIGTERM"),this.killViteWatch(e);try{u.kill("SIGTERM")}catch{}throw this.releasePort(e.agentPort),e.agentPort=0,e.apiServerPort=0,h}}resolveLimits(){if(this.resolvedLimits===void 0)this.resolvedLimits=je(this.opts.env??process.env);return this.resolvedLimits}watchHostLimits(){let e=(this.opts.env??process.env).SHOGO_HOST_LIMITS_FILE;if(!e||this.hostLimitsFile)return;this.hostLimitsFile=e,this.resolveLimits();try{let t=Qo(e),r=Xo(Zt(e),{persistent:!1},(o,n)=>{if(!n||n.toString()===t)this.applyHostLimitsChange()});this.hostLimitsWatcher=r,r.on("error",(o)=>{if(this.log.warn(`[WorkerRuntimeManager] host limits directory watch failed: ${o.message}`),this.hostLimitsWatcher!==r)return;r.close(),this.hostLimitsWatcher=null,this.watchHostLimitsByPolling(e)})}catch(t){this.log.warn(`[WorkerRuntimeManager] host limits directory watch failed: ${t?.message??t}`)}this.watchHostLimitsByPolling(e)}watchHostLimitsByPolling(e){if(this.hostLimitsPolling)return;this.hostLimitsPolling=!0,zo(e,{interval:2000},()=>this.applyHostLimitsChange())}applyHostLimitsChange(){let e=this.opts.env??process.env,t;try{t=je(e)}catch(i){this.log.warn(`[WorkerRuntimeManager] host limits re-read failed: ${i?.message??i}`);return}let r=this.resolvedLimits,o=r===t||!!r&&!!t&&r.memoryMB===t.memoryMB&&r.osMemoryMB===t.osMemoryMB&&r.cpuPercent===t.cpuPercent&&r.tasksMax===t.tasksMax;if(this.resolvedLimits=t,!t||o)return;let n=[...this.runtimes.values()].filter((i)=>i.pid&&i.status!=="stopped");if(n.length===0)return;this.log.log(`[WorkerRuntimeManager] agent-command budget changed — memory cap now ${t.osMemoryMB}M (${t.memoryMB}M runtime + ${t.osMemoryMB-t.memoryMB}M commands)`);for(let i of n)try{Bt({pid:i.pid,scopeName:i.cgroupScope??null,limits:t,logger:this.log})}catch(s){this.log.warn(`[WorkerRuntimeManager] live memory cap for ${i.projectId} failed: ${s?.message??s}`)}}unwatchHostLimits(){if(!this.hostLimitsFile)return;this.hostLimitsWatcher?.close(),this.hostLimitsWatcher=null,Yo(this.hostLimitsFile),this.hostLimitsPolling=!1,this.hostLimitsFile=null}applyPostSpawnLimits(e,t,r){let o=e.pid;if(!o)return;if(process.platform==="linux"){if(r)return;if(Ht({pid:o,limits:t,logger:this.log}))return}else if(process.platform==="win32"){if(Gt({pid:o,limits:t,logger:this.log}))return}let n=this.execPidsFile(e);e.limitWatchdog=new He({pid:o,ceilingMB:t.memoryMB,excludePids:()=>Kt(n),logger:this.log,onBreach:(i)=>this.handleLimitBreach(e,i,t.memoryMB)}),e.limitWatchdog.start()}handleLimitBreach(e,t,r){if(this.log.warn(`[WorkerRuntimeManager] runtime ${e.projectId} exceeded memory ceiling (${t}MB > ${r}MB) — restarting`),e.lastError=`memory ceiling exceeded (${t}MB > ${r}MB)`,e.limitWatchdog)e.limitWatchdog.stop(),e.limitWatchdog=null;this.killProcessGroup(e,"SIGTERM"),this.killViteWatch(e);try{e.proc?.kill("SIGTERM")}catch{}let o=setTimeout(()=>{this.killProcessGroup(e,"SIGKILL"),this.killViteWatch(e)},5000);try{o.unref?.()}catch{}}killProcessGroup(e,t){if(!e.pid)return;if(process.platform==="win32"){try{we("taskkill",["/F","/T","/PID",String(e.pid)],{stdio:"ignore",windowsHide:!0})}catch{}return}try{process.kill(-e.pid,t)}catch{}}armGraceTimer(e){if(e.graceTimer)clearTimeout(e.graceTimer),e.graceTimer=null;e.graceTimer=setTimeout(()=>{if(e.graceTimer=null,e.consecutiveFailures>0)e.consecutiveFailures=0;e.breakerTrips=0,e.restartTimestamps=[]},hn);try{e.graceTimer.unref?.()}catch{}}buildEnv(e,t){let r=e.spawnConfig,o={...this.opts.env??process.env,PROJECT_ID:e.projectId,PORT:String(e.agentPort),API_SERVER_PORT:String(e.apiServerPort),SKILL_SERVER_PORT:String(e.apiServerPort),WORKSPACE_API_PORT_BASE:String(e.agentPort+on),NODE_ENV:"production",STARTUP_TIME:String(Date.now()),SHOGO_CLOUD_URL:r.cloudUrl,SHOGO_API_URL:r.cloudUrl,SHOGO_API_KEY:r.apiKey,RUNTIME_AUTH_SECRET:sr(e.projectId),WEBHOOK_TOKEN:Sn(e.projectId)};if(r.projectDir)o.PROJECT_DIR=r.projectDir,o.WORKSPACE_DIR=r.projectDir;if(this.shouldDeferToCloudSync(e.projectId))o.SHOGO_CLOUD_SYNC="1";if(r.aiProxyUrl)o.AI_PROXY_URL=r.aiProxyUrl;if(r.aiProxyToken)o.AI_PROXY_TOKEN=r.aiProxyToken;if(r.techStackId)o.TECH_STACK_ID=r.techStackId;if(r.name)o.AGENT_NAME=r.name;if(r.workspaceId)o.WORKSPACE_ID=r.workspaceId;if(!o.TREE_SITTER_WASM_DIR)o.TREE_SITTER_WASM_DIR=X(Zt(t),"tree-sitter-wasm");if(r.extraEnv)Object.assign(o,r.extraEnv);let n=this.resolveLimits();if(n){Lt(o,n.memoryMB),o.SHOGO_EXEC_PIDS_FILE=this.execPidsFile(e);let i=this.opts.env??process.env;if(i.SHOGO_HOST_LIMITS_FILE)o.SHOGO_HOST_LIMITS_FILE=i.SHOGO_HOST_LIMITS_FILE;else o.SHOGO_EXEC_MEMORY_MB??=String(n.memoryMB)}return o}execPidsFile(e){return X(zt(),"shogo-exec-pids",`${e.projectId.replace(/[^\w.-]/g,"_")}.json`)}killViteWatch(e){let t=e.spawnConfig.projectDir;if(!t)return;try{Qt(t,{logger:this.log})}catch(r){this.log.warn(`[WorkerRuntimeManager] Vite watch cleanup for ${e.projectId} failed: ${r?.message??r}`)}}reapChildLedgerFor(e,t){if(!t||process.platform==="win32")return;try{let{reaped:r,dropped:o}=en(t,{logger:this.log});if(r.length>0)this.log.log(`[WorkerRuntimeManager] Reaped ${r.length} leftover child group(s) for ${e}: `+r.map((n)=>`${n.label}(pgid=${n.pgid})`).join(", "));if(o.length>0)this.log.log(`[WorkerRuntimeManager] Dropped ${o.length} stale ledger entr(ies) for ${e} (process gone or pid reused)`)}catch(r){this.log.warn(`[WorkerRuntimeManager] Child ledger reap for ${e} failed: ${r?.message??r}`)}}sweepOrphanedViteWatches(){let e=this.opts.autoPull?.projectsDir;if(!e)return;let t;try{t=ur(e,{withFileTypes:!0})}catch{return}for(let r of t){if(!r.isDirectory()||this.runtimes.has(r.name))continue;let o=X(e,r.name);try{let n=Qt(o,{logger:this.log});if(n)this.log.log(`[WorkerRuntimeManager] Reaped orphaned Vite watch for ${r.name} (pid=${n.pid})`);this.reapChildLedgerFor(r.name,o)}catch(n){this.log.warn(`[WorkerRuntimeManager] Vite watch startup sweep failed for ${r.name}: ${n?.message??n}`)}}}resolveCwd(e){let t=e.spawnConfig;if(t.projectDir&&et(t.projectDir))return t.projectDir;let r=this.opts.runtimeWorkDir??X(zt(),"shogo-runtime",e.projectId);return Xt(r,{recursive:!0}),r}handleExit(e,t,r){let o=r===null&&t===0,n=e.status==="stopping"||this.stopped;if(this.log.log(`[WorkerRuntimeManager] runtime ${e.projectId} exited (code=${t}, signal=${r})`),!n){let p=e.stderrTail?.length?`
|
|
14
14
|
${e.stderrTail.join(`
|
|
15
|
-
`)}`:" (nothing on stderr)";this.log.warn(`[WorkerRuntimeManager] runtime ${e.projectId} exited WITHOUT a stop request (code=${t}, signal=${r}, uptime ${Math.round((Date.now()-e.startedAt)/1000)}s, last activity ${this.activityDecision(e.projectId).countsText}) — last stderr:${u}`)}if(e.proc=null,e.graceTimer)clearTimeout(e.graceTimer),e.graceTimer=null;if(e.limitWatchdog)e.limitWatchdog.stop(),e.limitWatchdog=null;if(this.killViteWatch(e),e.status==="stopping"||this.stopped){e.status="stopped",e.pid=null,this.releasePort(e.agentPort),e.agentPort=0,e.apiServerPort=0;return}if(o){e.status="stopped",e.pid=null,this.releasePort(e.agentPort),e.agentPort=0,e.apiServerPort=0,this.runtimes.delete(e.projectId),this.notifyGone(e.projectId,{reason:"exited",code:t,signal:r});return}this.killProcessGroup(e,"SIGKILL"),this.killViteWatch(e),this.reapChildLedgerFor(e.projectId,e.spawnConfig.projectDir),e.pid=null;let i=Date.now(),s=i-e.lastFailureAt<=rr;e.consecutiveFailures=s?e.consecutiveFailures+1:1,e.lastFailureAt=i,e.restarts+=1,e.lastError=`exited code=${t} signal=${r}`,e.restartTimestamps.push(i),e.restartTimestamps=e.restartTimestamps.filter((u)=>i-u<=or);let a=e.consecutiveFailures>=tr,l=e.restartTimestamps.length>=rn;if(a||l){e.status="failed",e.breakerTrips+=1;let u=this.breakerCooldownMs(e.breakerTrips),p=a?`${e.consecutiveFailures} consecutive non-clean exits within ${Math.round(rr/1000)}s`:`${e.restartTimestamps.length} non-clean exits within the last ${Math.round(or/60000)}m (slow-bleed loop — gaps between crashes kept clearing the tight-loop window)`;if(e.lastError=`Circuit breaker tripped (trip #${e.breakerTrips}): ${p} (last: code=${t} signal=${r}). Most recent on macOS is jetsam OOM (signal=SIGKILL with code=null); the previous incarnation's vite/tsserver/preview-manager children were reaped to prevent further RSS growth. Will auto-retry in ~${Math.round(u/1000)}s; call resetFailure(${e.projectId}) to retry immediately, or stop(${e.projectId}) to give up.`,this.releasePort(e.agentPort),e.agentPort=0,e.apiServerPort=0,e.restartTimer)clearTimeout(e.restartTimer),e.restartTimer=null;if(e.idleTimer)clearTimeout(e.idleTimer),e.idleTimer=null;this.log.error(`[WorkerRuntimeManager] ${e.lastError}`),this.notifyGone(e.projectId,{reason:"failed",code:t,signal:r});return}let c=this.restartBackoffMs(e.restarts);if(e.status="restarting",this.log.warn(`[WorkerRuntimeManager] restarting ${e.projectId} in ${Math.round(c/1000)}s (restart #${e.restarts}, consecutive failures ${e.consecutiveFailures}/${tr})`),e.restartTimer)clearTimeout(e.restartTimer);e.restartTimer=setTimeout(()=>{if(e.restartTimer=null,e.status==="failed"||this.stopped)return;e.startPromise=this.doStart(e).then((u)=>(e.startPromise=null,u)).catch((u)=>(e.startPromise=null,this.log.error(`[WorkerRuntimeManager] restart of ${e.projectId} failed: ${u?.message??u}`),e))},c);try{e.restartTimer.unref?.()}catch{}}restartBackoffMs(e){let t=Math.min(en*Math.pow(2,Math.max(0,e-1)),tn),r=t*0.2*Math.random();return t+r}armIdleTimer(e){if(e.idleTimer)clearTimeout(e.idleTimer),e.idleTimer=null;let t=this.opts.idleMs??zo;if(!Number.isFinite(t)||t<=0)return;e.idleTimer=setTimeout(async()=>{e.idleTimer=null;let r=Date.now()-e.lastUsedAt;if(r<t){this.armIdleTimer(e);return}let o=await this.freshActivityDecision(e.projectId);if(this.runtimes.get(e.projectId)!==e||e.status!=="running"||e.idleTimer)return;if(o.dead){this.log.log(`[WorkerRuntimeManager] reaping dead ${e.projectId} (${ue(o)})`),this.stop(e.projectId,"SIGTERM","dead").catch((n)=>{this.log.warn(`[WorkerRuntimeManager] reap stop failed: ${n?.message??n}`)});return}if(o.busy){this.log.log(`[WorkerRuntimeManager] not idle-evicting ${e.projectId}: ${o.reason} (${ue(o)})`),this.touch(e.projectId);return}this.log.log(`[WorkerRuntimeManager] idle-evicting ${e.projectId} after ${Math.round(r/1000)}s`),this.stop(e.projectId,"SIGTERM","idle-evict").catch((n)=>{this.log.warn(`[WorkerRuntimeManager] idle stop failed: ${n?.message??n}`)})},t)}async hasActiveWork(e){let t=this.runtimes.get(e);if(!t)return!1;let r=await this.activeWork(t);return!!r&&r.turns+r.tasks+r.streams+r.children>0}async activeWork(e){if(e.status!=="running"||!e.agentPort)return null;try{let t=await fetch(`http://127.0.0.1:${e.agentPort}/pool/activity`,{headers:e.runtimeToken?{"x-runtime-token":e.runtimeToken}:{},signal:AbortSignal.timeout(Qo)});if(!t.ok)return null;let r=await t.json();if(!(typeof r.activeTurns==="number"||typeof r.activeTasks==="number"||typeof r.activeStreams==="number"||typeof r.activeChildren==="number"))return null;return{turns:r.activeTurns??0,tasks:r.activeTasks??0,streams:r.activeStreams??0,children:r.activeChildren??0}}catch{return null}}async sampleActivity(e){let t=Date.now(),r=await this.activeWork(e),o=e.activity,n=r?{counts:r,at:t,ok:!0}:{counts:o?.ok?o.counts:Xe,at:t,ok:!1,error:e.status!=="running"?`status=${e.status}`:e.agentPort?"no answer from /pool/activity":"no agent port",failingSince:o&&!o.ok?o.failingSince??t:t};return e.activity=n,n}async sampleAllActivity(){let e=[...this.runtimes.values()].filter((t)=>t.status==="running"&&!!t.agentPort);if(e.length===0)return;await Promise.all(e.map((t)=>this.sampleActivity(t).catch(()=>null)))}ensureActivitySampler(){if(this.activityTimer)return;let e=setInterval(()=>{this.sampleAllActivity()},qt);e.unref?.(),this.activityTimer=e}activityDecision(e,t){let r=Date.now(),o=this.runtimes.get(e);if(!o)return ze({now:r,startedAt:r,status:"stopped",processExited:!0});return ze({now:r,startedAt:o.startedAt,status:o.status,sample:o.activity??null,terminalSessions:t?.terminalSessions,inflightCalls:t?.inflightCalls,processExited:this.processExited(o)})}processExited(e){if(e.status==="stopped"||e.status==="failed")return!0;if(e.proc)return!1;return e.status==="running"}async freshActivityDecision(e,t){let r=this.runtimes.get(e);if(r&&r.status==="running"&&r.agentPort){let o=r.activity;if(!o||Date.now()-o.at>Ye)await this.sampleActivity(r).catch(()=>null)}return this.activityDecision(e,t)}async refreshActivity(e){let t=this.runtimes.get(e);if(!t||t.status!=="running"||!t.agentPort)return;let r=t.activity;if(r&&Date.now()-r.at<=Ye)return;await this.sampleActivity(t).catch(()=>null)}async allocatePort(){let e=tt-et,t=Math.min(e,50);for(let r=0;r<t;r++){let o=et+Math.floor(Math.random()*e);if(o+Te-1>tt)continue;let n=!0;for(let a=0;a<Te;a++)if(this.usedPorts.has(o+a)){n=!1;break}if(!n)continue;let i=await this.isPortListening(o),s=await this.isPortListening(o+Qt);if(i||s)continue;for(let a=0;a<Te;a++)this.usedPorts.add(o+a);return o}throw Error(`Cannot allocate port in range ${et}-${tt} after ${t} attempts`)}releasePort(e){if(!e)return;for(let t=0;t<Te;t++)this.usedPorts.delete(e+t)}async isPortListening(e){let t=new AbortController,r=setTimeout(()=>t.abort(),250);try{return await fetch(`http://127.0.0.1:${e}/`,{method:"HEAD",signal:t.signal}),clearTimeout(r),!0}catch{return clearTimeout(r),!1}}tcpProbe(e){return new Promise((t)=>{let r=!1,o=(i)=>{if(r)return;r=!0;try{n.destroy()}catch{}t(i)},n=Vo({host:"127.0.0.1",port:e});n.setTimeout(un),n.once("connect",()=>o(!0)),n.once("error",()=>o(!1)),n.once("timeout",()=>o(!1))})}async waitForHealth(e,t){let{agentPort:r,proc:o}=e;if(!o)throw Error(`waitForHealth: slot ${e.projectId} has no spawned process`);let n=Date.now(),i=n+t,s=null,a=!1,l=0,c=0,u=0,p=0,m=n;while(Date.now()<i){if(p++,o.exitCode!==null||o.signalCode!=null||o.killed)throw Error(`agent-runtime exited (code=${o.exitCode}, signal=${o.signalCode}) before becoming healthy on port ${r}`);c++;let f=new AbortController,y=setTimeout(()=>f.abort(),1500);try{let d=await fetch(`http://127.0.0.1:${r}/health`,{method:"GET",signal:f.signal});if(clearTimeout(y),d.ok){this.log.log(`[WorkerRuntimeManager] /health ready for ${e.projectId} on port ${r} (HTTP ${d.status} after ${Date.now()-n}ms, ${p} iter, ${c} http)`);return}s=`HTTP /health returned ${d.status}`}catch(d){clearTimeout(y);let v=d?.name??"Error",Q=d?.code??d?.cause?.code;s=`HTTP /health failed: ${v}${Q?`(${Q})`:""}: ${d?.message??d}`}if(u++,a=await this.tcpProbe(r),a){l=Date.now();let d=Date.now()-e.lastStdoutAt;if(d>=nr)throw Error(`agent-runtime wedged on port ${r}: TCP listening but stdout silent for ${d}ms (> ${nr}ms window); ${c} /health attempts, last error: ${s??"n/a"}`)}let g=Date.now();if(g-m>=cn){let d=g-n,v=g-e.lastStdoutAt,Q=l>0?g-l:null;this.log.log(`[WorkerRuntimeManager] still waiting for /health on ${e.projectId} port ${r} (${(d/1000).toFixed(1)}s elapsed, tcpListening=${a}${Q!=null?`(${Q}ms ago)`:""}, lastStdout=${v}ms ago, ${c} http, ${u} tcp, lastError=${s??"n/a"})`),m=g}await new Promise((d)=>setTimeout(d,an))}if(o.exitCode!==null||o.signalCode!=null||o.killed)throw Error(`agent-runtime exited (code=${o.exitCode}, signal=${o.signalCode}) before becoming healthy on port ${r}`);throw Error(`Timeout waiting for agent-runtime /health on port ${r} after ${p} iter (httpAttempts=${c}, tcpAttempts=${u}, tcpListening=${a}, lastError=${s??"n/a"})`)}async waitForExit(e,t){if(e.exitCode!==null||e.signalCode!=null||e.killed)return;await new Promise((r)=>{let o=setTimeout(()=>{try{e.kill("SIGKILL")}catch{}r()},t);e.once("exit",()=>{clearTimeout(o),r()})})}snapshot(e){return{projectId:e.projectId,status:e.status,agentPort:e.agentPort||void 0,apiServerPort:e.apiServerPort||void 0,pid:e.proc?.pid,startedAt:e.startedAt||void 0,lastUsedAt:e.lastUsedAt,restarts:e.restarts,lastError:e.lastError}}}import{hostname as it,platform as st,arch as at}from"node:os";function X(){if(typeof __SHOGO_WORKER_VERSION__==="string"&&__SHOGO_WORKER_VERSION__.length>0)return`shogo-cli/${__SHOGO_WORKER_VERSION__}`;return"shogo-cli/unknown"}import{readFileSync as gn,writeFileSync as hn,existsSync as fn,chmodSync as yn}from"node:fs";import{randomUUID as wn}from"node:crypto";function Ie(){if(fn(ee)){let t=gn(ee,"utf-8").trim();if(t)return t}B();let e=wn();return hn(ee,e,{mode:384}),yn(ee,384),e}class gr extends Error{code="TUNNEL_WS_HEADERS_UNSUPPORTED";constructor(){super("Tunnel WebSocket auth requires a runtime with WebSocket header support (Bun or Node >= 21). Current runtime does not support WebSocket constructor headers.");this.name="TunnelWebSocketHeaderSupportError"}}var pe=60,xe=300,lt=3,vn=lt,Sn=1800000,bn=25000,pr=1000,dr=60000,mr=3;class ct{opts;log;pollTimer=null;ws=null;wsIdleTimer=null;heartbeatTimer=null;stopped=!1;currentPollInterval=pe;wsReconnectAttempt=0;lastHeartbeatError=null;consecutiveAuthFailures=0;consecutiveAuthSuccesses=0;serverPublishedWsUrl=null;lastInstanceId=null;activeAbortControllers=new Map;constructor(e){this.opts=e,this.log=e.logger??console}get instanceId(){return this.lastInstanceId}start(){if(!this.opts.apiKey){this.log.log("[WorkerTunnel] No API key set, skipping tunnel");return}this.stopped=!1,this.wsReconnectAttempt=0,this.currentPollInterval=pe,this.log.log("[WorkerTunnel] Starting heartbeat polling to Shogo Cloud..."),this.heartbeatLoop()}stop(){if(this.stopped=!0,this.pollTimer)clearTimeout(this.pollTimer),this.pollTimer=null;this.cleanupWs(),this.log.log("[WorkerTunnel] Tunnel stopped")}isConnected(){if(this.ws!==null&&this.ws.readyState===WebSocket.OPEN)return!0;return!this.stopped&&!!this.opts.apiKey&&this.lastHeartbeatError===null&&this.pollTimer!==null}getCloudUrl(){return this.opts.cloudUrl.replace(/\/$/,"")}deviceIdValue=null;get deviceId(){if(this.deviceIdValue!==null)return this.deviceIdValue;let e=(this.opts.deviceId??"").trim();if(!e)try{e=Ie()}catch{e=""}return this.deviceIdValue=e,e}getWsBaseUrl(){let e=(this.opts.wsUrlOverride||process.env.SHOGO_TUNNEL_WS_URL||"").trim();if(e)return e.replace(/\/$/,"");if(this.serverPublishedWsUrl)return this.serverPublishedWsUrl.replace(/\/$/,"");return this.getCloudUrl().replace(/^http/,"ws")}buildWsUrl(){return`${this.getWsBaseUrl()}/api/instances/ws`}supportsWebSocketConstructorHeaders(e=globalThis){if(typeof e.Bun<"u"||typeof e.process?.versions?.bun==="string")return!0;let t=e.process?.versions?.node;if(t){if(parseInt(t.split(".")[0]??"0",10)>=21)return!0}return!1}createTunnelWebSocket(e,t,r=globalThis){if(!this.supportsWebSocketConstructorHeaders(r))throw new gr;return new WebSocket(e,t)}getReconnectDelayMs(){let e=Math.min(pr*Math.pow(2,this.wsReconnectAttempt),dr),t=e*0.2*Math.random();return e+t}async collectMetadata(){try{await this.opts.resolver.refresh?.()}catch{}let e={hostname:it(),os:st(),arch:at(),uptime:process.uptime(),protocolVersion:mr,tunnelStatus:this.ws?.readyState===WebSocket.OPEN?"connected":"polling",kind:this.opts.kind??"cli-worker"};try{let t=this.opts.resolver.getActiveProjects();e.activeProjects=t.length,e.projects=t.map((r)=>{let o=this.opts.resolver.status(r);return{projectId:r,status:o?.status??"unknown",agentPort:o?.agentPort}})}catch{e.activeProjects=0}try{let t=await this.opts.resolver.machineSnapshot?.();if(t)e.machine=t}catch{}return e}async sendHeartbeat(){let e=this.getCloudUrl(),t=await this.collectMetadata(),r=it(),o=this.opts.name??r,n=await fetch(`${e}/api/instances/heartbeat`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":X(),"x-api-key":this.opts.apiKey,"x-shogo-kind":this.opts.kind??"cli-worker",...this.deviceId?{"x-shogo-device-id":this.deviceId}:{}},body:JSON.stringify({hostname:r,name:o,os:st(),arch:at(),kind:this.opts.kind??"cli-worker",metadata:t})});if(!n.ok)throw Error(`Heartbeat failed: HTTP ${n.status}`);let i=await n.json();if(typeof i.instanceId==="string"&&i.instanceId.length>0)this.lastInstanceId=i.instanceId;if(typeof i.wsUrl==="string"&&i.wsUrl.length>0){if(i.wsUrl!==this.serverPublishedWsUrl)this.log.log(`[WorkerTunnel] Cloud advertised tunnel WS URL: ${i.wsUrl}`);this.serverPublishedWsUrl=i.wsUrl}return i}scheduleNextPoll(e){if(this.stopped)return;if(this.pollTimer)clearTimeout(this.pollTimer);let t=(e??this.currentPollInterval)*1000;this.pollTimer=setTimeout(()=>void this.heartbeatLoop(),t)}async heartbeatLoop(){if(this.stopped)return;if(this.ws&&this.ws.readyState===WebSocket.OPEN){this.scheduleNextPoll(this.currentPollInterval);return}try{let e=await this.sendHeartbeat(),t=e.nextPollIn||pe;if(this.consecutiveAuthFailures>=lt){if(this.consecutiveAuthSuccesses++,this.consecutiveAuthSuccesses<vn){this.currentPollInterval=xe,this.scheduleNextPoll();return}}if(this.currentPollInterval=t,this.lastHeartbeatError)this.log.log("[WorkerTunnel] Heartbeat recovered"),this.lastHeartbeatError=null;if(this.consecutiveAuthFailures=0,this.consecutiveAuthSuccesses=0,e.wsRequested&&!this.ws){this.log.log("[WorkerTunnel] Cloud requested WebSocket — connecting..."),this.connectWs();return}}catch(e){let t=e?.message??String(e);if(/HTTP 40[13]\b/.test(t))this.consecutiveAuthFailures++,this.consecutiveAuthSuccesses=0;else this.consecutiveAuthFailures=0,this.consecutiveAuthSuccesses=0;if(t!==this.lastHeartbeatError)this.log.error(`[WorkerTunnel] Heartbeat error: ${t}`),this.lastHeartbeatError=t;if(this.consecutiveAuthFailures>=lt){let o=`tunnel saw ${this.consecutiveAuthFailures} consecutive auth failures from Shogo Cloud`;try{this.opts.onAuthRevoked?.(o)}catch(n){this.log.warn(`[WorkerTunnel] onAuthRevoked threw: ${n?.message??n}`)}if(this.currentPollInterval!==xe)this.log.warn(`[WorkerTunnel] ${this.consecutiveAuthFailures} consecutive auth failures — `+`backing off to ${xe}s. Re-authenticate with \`shogo login\`.`);this.currentPollInterval=xe}else this.currentPollInterval=pe}this.scheduleNextPoll()}async handleRequest(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;this.resetWsIdleTimer();let t=new AbortController;this.activeAbortControllers.set(e.requestId,t);try{let r=await this.resolveLocalUrl(e.path,e.projectId);if(!r){let s=this.opts.resolver.describeRejection?await this.opts.resolver.describeRejection(e.path,e.projectId):{code:"NO_LOCAL_RUNTIME",message:`no local runtime available for path: ${e.path}`};this.sendFrame({type:"response",requestId:e.requestId,status:502,headers:{"content-type":"application/json"},body:JSON.stringify({code:s.code,message:s.message,path:e.path})});return}let o={...e.headers??{}};if(e.projectId&&(e.path.startsWith("/agent/")||e.path==="/agent")){let s=await this.opts.resolver.deriveRuntimeToken(e.projectId);if(s)o["x-runtime-token"]=s}let n={method:e.method,headers:o,signal:t.signal};if(e.body&&e.method!=="GET"&&e.method!=="HEAD")n.body=e.body;let i=await fetch(r,n);if(e.stream){let s=i.body?.getReader();if(!s){this.sendFrame({type:"stream-error",requestId:e.requestId,error:"No response body for stream"});return}let a=new TextDecoder;try{while(!0){let{done:l,value:c}=await s.read();if(l)break;if(this.ws?.readyState!==WebSocket.OPEN)break;this.sendFrame({type:"stream-chunk",requestId:e.requestId,data:a.decode(c,{stream:!0})})}if(this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-end",requestId:e.requestId})}catch(l){if(l?.name!=="AbortError"&&this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-error",requestId:e.requestId,error:l?.message??String(l)})}}else{let s=await i.text(),a={};i.headers.forEach((l,c)=>{a[c]=l}),this.sendFrame({type:"response",requestId:e.requestId,status:i.status,headers:a,body:s})}}catch(r){if(r?.name==="AbortError")return;if(this.ws?.readyState!==WebSocket.OPEN)return;let o=e.stream?{type:"stream-error",requestId:e.requestId,error:r?.message??String(r)}:{type:"response",requestId:e.requestId,status:502,body:JSON.stringify({error:r?.message??String(r)})};this.sendFrame(o)}finally{this.activeAbortControllers.delete(e.requestId)}}async resolveLocalUrl(e,t){return this.opts.resolver.resolveLocalUrl(e,t)}sendFrame(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;try{this.ws.send(JSON.stringify(e))}catch(t){this.log.warn(`[WorkerTunnel] Frame send failed: ${t?.message??t}`)}}resetWsIdleTimer(){if(this.wsIdleTimer)clearTimeout(this.wsIdleTimer);this.wsIdleTimer=setTimeout(()=>{if(this.ws&&this.ws.readyState===WebSocket.OPEN){this.log.log("[WorkerTunnel] WebSocket idle timeout — closing, returning to polling");try{this.ws.close(1000,"Idle timeout")}catch{}}},Sn)}startWsHeartbeat(){if(this.heartbeatTimer)clearInterval(this.heartbeatTimer);this.heartbeatTimer=setInterval(async()=>{if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;try{let e=await this.collectMetadata();this.sendFrame({type:"heartbeat",metadata:e})}catch{}},bn)}connectWs(){if(this.stopped||this.ws)return;let e=this.buildWsUrl(),t=it(),r=st(),o=at(),n=this.opts.name??t;this.log.log(`[WorkerTunnel] Opening WebSocket to ${e} (hostname=${t})`);let i={headers:{Authorization:`Bearer ${this.opts.apiKey}`,"User-Agent":X(),"x-shogo-hostname":t,"x-shogo-name":n,"x-shogo-os":r,"x-shogo-arch":o,"x-shogo-kind":this.opts.kind??"cli-worker",...this.deviceId?{"x-shogo-device-id":this.deviceId}:{}}},s;try{s=this.createTunnelWebSocket(e,i)}catch(a){this.log.error(`[WorkerTunnel] WebSocket creation failed: ${a?.message??a}`),this.ws=null,this.scheduleNextPoll(5);return}this.ws=s,s.onopen=()=>{this.log.log("[WorkerTunnel] WebSocket connected — session active"),this.wsReconnectAttempt=0,this.startWsHeartbeat(),this.resetWsIdleTimer()},s.onmessage=(a)=>{let l;try{let c=typeof a.data==="string"?a.data:a.data.toString();l=JSON.parse(c)}catch{return}if(l.type==="ping"){this.sendFrame({type:"pong"}),this.resetWsIdleTimer();return}if(l.type==="cancel"){let c=this.activeAbortControllers.get(l.requestId);if(c)c.abort();return}if(l.type==="request"){this.handleRequest(l).catch((c)=>{this.log.error(`[WorkerTunnel] Error handling request: ${c?.message??c}`)});return}},s.onclose=(a)=>{if(this.log.log(`[WorkerTunnel] WebSocket closed: code=${a.code} reason=${a.reason||"none"}`),this.cleanupWs(),this.stopped)return;if(a.code===1000||a.code===4000)this.scheduleNextPoll(this.currentPollInterval);else{this.wsReconnectAttempt++;let l=this.getReconnectDelayMs();this.log.log(`[WorkerTunnel] Reconnecting in ${Math.round(l/1000)}s (attempt ${this.wsReconnectAttempt})`),this.scheduleNextPoll(Math.ceil(l/1000))}},s.onerror=(a)=>{this.log.error("[WorkerTunnel] WebSocket error:",a?.message??"unknown")}}cleanupWs(){if(this.heartbeatTimer)clearInterval(this.heartbeatTimer),this.heartbeatTimer=null;if(this.wsIdleTimer)clearTimeout(this.wsIdleTimer),this.wsIdleTimer=null;for(let[,e]of this.activeAbortControllers)try{e.abort()}catch{}this.activeAbortControllers.clear(),this.ws=null}_testing(){let e=this;return{sendHeartbeat:()=>e.sendHeartbeat(),heartbeatLoop:()=>e.heartbeatLoop(),connectWs:()=>e.connectWs(),cleanupWs:()=>e.cleanupWs(),handleRequest:(t)=>e.handleRequest(t),installFakeWs:(t)=>{e.ws=t},getCloudUrl:()=>e.getCloudUrl(),getWsBaseUrl:()=>e.getWsBaseUrl(),buildWsUrl:()=>e.buildWsUrl(),getReconnectDelayMs:()=>e.getReconnectDelayMs(),supportsWebSocketConstructorHeaders:(t)=>e.supportsWebSocketConstructorHeaders(t),createTunnelWebSocket:(t,r,o)=>e.createTunnelWebSocket(t,r,o),DEFAULT_POLL_INTERVAL_S:pe,BACKOFF_BASE_MS:pr,BACKOFF_MAX_MS:dr,TUNNEL_PROTOCOL_VERSION:mr,get currentPollInterval(){return e.currentPollInterval},set currentPollInterval(t){e.currentPollInterval=t},get wsReconnectAttempt(){return e.wsReconnectAttempt},set wsReconnectAttempt(t){e.wsReconnectAttempt=t},get ws(){return e.ws},get stopped(){return e.stopped},get serverPublishedWsUrl(){return e.serverPublishedWsUrl},set serverPublishedWsUrl(t){e.serverPublishedWsUrl=t}}}}async function hr(e){let t=D({name:e.name,workerDir:e.workerDir,apiKey:e.apiKey,cloudUrl:e.cloudUrl,port:e.port?parseInt(e.port,10):void 0,projectsDir:e.projectsDir}),r=xt(e.proxy);if(!e.foreground)return Rn({cfg:t,proxy:r,flags:e});let o=K({flag:e.runtimeBin});if(!o)console.error(h.red(q({flag:e.runtimeBin}))),process.exit(1);if(Fe(process.env,r),e.debug){if(!await At(_t({cloudUrl:t.cloudUrl,apiKey:t.apiKey,workerDir:t.workerDir,proxy:r})))process.exit(1)}let n=!e.noAutoPull,i=!e.noGit;if(console.log(h.bold(`
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
Shogo Worker — Started`)),console.log(h.dim(" pid: ")+a),console.log(h.dim(" name: ")+e.name),console.log(h.dim(" logs: ")+"~/.shogo/logs/worker.log"),console.log(h.dim(" stop: ")+"shogo worker stop")}function Pn(e){let t=["worker","start","--foreground"];if(e.name)t.push("--name",e.name);if(e.workerDir)t.push("--worker-dir",e.workerDir);if(e.apiKey)t.push("--api-key",e.apiKey);if(e.cloudUrl)t.push("--cloud-url",e.cloudUrl);if(e.port)t.push("--port",e.port);if(e.proxy)t.push("--proxy",e.proxy);if(e.project)t.push("--project",e.project);if(e.runtimeBin)t.push("--runtime-bin",e.runtimeBin);if(e.debug)t.push("--debug");if(e.noAutoPull)t.push("--no-auto-pull");if(e.projectsDir)t.push("--projects-dir",e.projectsDir);if(e.noGit)t.push("--no-git");return t}function Tn(){let e=process.execPath,t=/\bbun(?:-[^/\\]*)?$/.test(e)||typeof globalThis.Bun<"u",r=process.argv[1];if(r&&kn(r)){let n=/\.ts$/.test(r);return{entry:r,runner:t?"bun":n?"tsx":"node"}}return{entry:new URL("../../bin/shogo.mjs",import.meta.url).pathname,runner:t?"bun":"node"}}import fr from"picocolors";async function yr(){let{killedPid:e}=$t("SIGTERM");if(e===null){console.log(fr.dim("No worker running."));return}console.log(fr.green(`✓ Worker stopped (pid=${e}).`))}import N from"picocolors";async function wr(){let e=ve();if(!e){console.log(N.yellow("● stopped")+N.dim(" (no pid file)"));return}if(!Se(e)){console.log(N.red("● dead")+N.dim(` (stale pid ${e})`));return}let t=G();if(console.log(N.green("● running")+N.dim(` (pid ${e})`)),t.name)console.log(N.dim(" name: ")+t.name);if(t.cloudUrl)console.log(N.dim(" cloud: ")+t.cloudUrl);if(t.workerDir)console.log(N.dim(" dir: ")+t.workerDir)}W();import{existsSync as $n,statSync as In}from"node:fs";import vr from"picocolors";async function Sr(e){let t=e.err?fe:he;if(!$n(t)){console.log(vr.dim("No logs yet."));return}let r=e.follow?["-F","-n","200",t]:["-n","200",t];if(T("tail",r,{stdio:"inherit"}).on("exit",(n)=>process.exit(n??0)),!e.follow){if(In(t).size===0)console.log(vr.dim("(log file is empty)"))}}import x from"picocolors";W();import{hostname as xn,platform as En,arch as Mn}from"node:os";import J from"picocolors";class A extends Error{kind;constructor(e,t){super(e);this.kind=t;this.name="CloudLoginError"}}async function br(e){let t=e.cloudUrl.replace(/\/$/,""),r=e.client??"cli",o=e.deviceName??xn(),n=e.devicePlatform??`${En()}-${Mn()}`,i=e.appVersion??Cn(),s=e.log??((d)=>console.log(d)),a=e.fetchImpl??fetch,l=await a(`${t}/api/cli/login/start`,{method:"POST",headers:{"content-type":"application/json","user-agent":X()},body:JSON.stringify({deviceId:e.deviceId,deviceName:o,devicePlatform:n,deviceAppVersion:i,workspaceId:e.workspaceId,client:r}),signal:AbortSignal.timeout(1e4)}).catch((d)=>{throw new A(`Cannot reach Shogo Cloud at ${t}: ${d?.message??d}`,"transport")});if(!l.ok){let d=await l.text().catch(()=>"");throw new A(`Cloud rejected /api/cli/login/start (HTTP ${l.status}): ${d||"no body"}`,"transport")}let c=await l.json().catch(()=>({}));if(!c?.ok||!c.state||!c.authUrl)throw new A(`Cloud returned a malformed start response: ${c?.error??JSON.stringify(c)}`,"transport");let u=_n(e.pollIntervalMs??c.pollIntervalMs),p=e.timeoutMs??c.expiresInMs,m=Date.now()+p;if(s(""),s(J.bold("Sign in to Shogo Cloud")),s(J.dim(" cloud: ")+t),s(J.dim(" device: ")+`${o} (${n})`),s(J.dim(" user code: ")+J.cyan(c.userCode)),s(""),s(" Open this URL in your browser to approve:"),s(" "+J.cyan(c.authUrl)),s(""),e.openBrowser!==!1)if(typeof e.openBrowser==="function")try{await e.openBrowser(c.authUrl)}catch{}else On(c.authUrl).catch(()=>{});s(J.dim("Waiting for approval..."));let f=e.installSignalHandlers??!0,y=()=>{g.abort()},g=new AbortController;if(e.abortSignal)if(e.abortSignal.aborted)g.abort();else e.abortSignal.addEventListener("abort",()=>g.abort(),{once:!0});if(f)process.once("SIGINT",y),process.once("SIGTERM",y);try{while(!0){if(g.signal.aborted)throw new A("Aborted by caller.","cancelled");if(Date.now()>=m)throw new A(`Timed out after ${Math.round(p/1000)}s waiting for approval.`,"timeout");let d=await a(`${t}/api/cli/login/poll?state=${encodeURIComponent(c.state)}`,{method:"GET",headers:{accept:"application/json","user-agent":X()},signal:AbortSignal.timeout(1e4)}).catch((v)=>(s(J.dim(` (poll error: ${v?.message??v} — retrying)`)),null));if(d&&d.ok){let v=await d.json().catch(()=>({}));if(v?.status==="approved"&&v.key)return{key:v.key,email:v.email??null,workspace:v.workspace??null,deviceId:v.deviceId??e.deviceId,workspaces:v.workspaces??null};if(v?.status==="denied")throw new A("Sign-in was denied in the browser.","denied");if(v?.status==="expired")throw new A("Sign-in request expired before it was approved.","expired")}await An(u,g.signal)}}finally{if(f)process.removeListener("SIGINT",y),process.removeListener("SIGTERM",y)}}function _n(e){if(!Number.isFinite(e)||e<=0)return 2000;return Math.min(Math.max(e,1000),1e4)}function An(e,t){return new Promise((r,o)=>{if(t?.aborted)return o(new A("Aborted by caller.","cancelled"));let n=setTimeout(()=>{t?.removeEventListener("abort",i),r()},e),i=()=>{clearTimeout(n),o(new A("Aborted by caller.","cancelled"))};t?.addEventListener("abort",i,{once:!0})})}function Cn(){return X()}function On(e){return new Promise((t)=>{let r=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",o=process.platform==="win32"?["/c","start",'""',e]:[e];try{let n=T(r,o,{stdio:"ignore",detached:!0});n.on("error",()=>t()),n.unref(),t()}catch{t()}})}var Dn="https://studio.shogo.ai";async function kr(e){let t=G(),r=(e.cloudUrl||t.cloudUrl||Dn).replace(/\/$/,""),o=e.apiKey||process.env.SHOGO_API_KEY;if(o){await Wn({key:o,cloudUrl:r,cfg:t,name:e.name});return}let n=Ie(),i;try{i=await br({cloudUrl:r,deviceId:n,deviceName:e.name,workspaceId:e.workspace,openBrowser:!e.noBrowser})}catch(s){if(s instanceof A){if(console.error(x.red(`✗ ${s.message}`)),s.kind==="transport")console.error(x.dim(" If your network blocks browsers, run with --api-key <key> instead."));process.exit(1)}throw s}if(t.apiKey=i.key,t.cloudUrl=r,e.name)t.name=e.name;if(ne(t),console.log(""),console.log(x.green("✓ Signed in to Shogo Cloud")),i.workspace)console.log(x.dim(" workspace: ")+i.workspace);if(i.email)console.log(x.dim(" email: ")+i.email);console.log(x.dim(" saved to: ")+"~/.shogo/config.json"),console.log(""),console.log(x.dim(" next: ")+"shogo worker start")}async function Wn({key:e,cloudUrl:t,cfg:r,name:o}){if(!/^shogo_sk_/.test(e))console.error(x.red('✗ API key should start with "shogo_sk_". Copy it verbatim from the API Keys page.')),process.exit(1);let n;try{let i=await fetch(`${t}/api/api-keys/validate`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({key:e}),signal:AbortSignal.timeout(1e4)});if(n=await i.json().catch(()=>({})),!i.ok||!n?.valid)console.error(x.red(`✗ ${n?.error||`Cloud rejected the key (HTTP ${i.status}).`}`)),process.exit(1)}catch(i){console.error(x.red(`✗ Cannot reach Shogo Cloud at ${t}: ${i?.message??i}`)),process.exit(1)}if(r.apiKey=e,r.cloudUrl=t,o)r.name=o;if(ne(r),console.log(x.green("✓ API key saved to ~/.shogo/config.json")),n.workspace?.name)console.log(x.dim(" workspace: ")+n.workspace.name);if(n.user?.email)console.log(x.dim(" email: ")+n.user.email);console.log(x.dim(" next: ")+"shogo worker start")}import de from"picocolors";async function Rr(){let e=G();if(Object.keys(e).length===0){console.log(de.dim("(no config — run `shogo login` or `shogo config set`)"));return}let t={...e,apiKey:e.apiKey?`***${e.apiKey.slice(-4)}`:void 0};console.log(de.dim(`file: ${H}`)),console.log(JSON.stringify(t,null,2))}async function Pr(e,t){let r=["apiKey","cloudUrl","name","workerDir","port"];if(!r.includes(e))console.error(de.red(`Unknown key: ${e}`)),console.error(de.dim(`Allowed: ${r.join(", ")}`)),process.exit(1);let o=G();o[e]=e==="port"?parseInt(t,10):t,ne(o),console.log(de.green(`✓ ${e} set`))}import b from"picocolors";W();import{createHash as Un}from"node:crypto";import{createWriteStream as Ln,existsSync as me,mkdirSync as ut,readFileSync as Ir,renameSync as Tr,rmSync as pt,writeFileSync as Fn}from"node:fs";import{tmpdir as Nn}from"node:os";import{dirname as jn,join as Ee}from"node:path";import{Readable as Hn}from"node:stream";import{pipeline as Bn}from"node:stream/promises";var Gn="https://github.com/shogo-labs/shogo-ai/releases/download";function xr(){let e=process.platform,t=process.arch,r;if(e==="darwin")r="darwin";else if(e==="linux")r="linux";else if(e==="win32")r="windows";else throw Error(`Unsupported platform: ${e}`);let o;if(t==="arm64")o="arm64";else if(t==="x64")o="x64";else throw Error(`Unsupported arch: ${t} (need arm64 or x64)`);return`${r}-${o}`}function Me(){if(!me(oe))return null;try{let e=Ir(oe,"utf-8");return JSON.parse(e)}catch{return null}}async function Kn(e,t){let r=t.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/releases\/download/);if(!r)throw Error(`Cannot auto-resolve latest version from non-GitHub baseUrl '${t}'. Pass --version explicitly.`);let o=r[1],n=r[2];if(e==="stable"){let u=`https://api.github.com/repos/${o}/${n}/releases/latest`,p=await fetch(u,{headers:{Accept:"application/vnd.github+json"}});if(!p.ok)throw Error(`GitHub API ${p.status} for ${u}`);let m=await p.json();if(!m.tag_name)throw Error("GitHub API did not return tag_name");return $r(m.tag_name)}let i=`https://api.github.com/repos/${o}/${n}/releases?per_page=30`,s=await fetch(i,{headers:{Accept:"application/vnd.github+json"}});if(!s.ok)throw Error(`GitHub API ${s.status} for ${i}`);let a=await s.json(),l=e==="beta"?"-beta":"-nightly",c=a.find((u)=>u.prerelease&&/^v\d+\.\d+\.\d+-/.test(u.tag_name)&&u.tag_name.includes(l));if(!c)throw Error(`No ${e} runtime release found`);return $r(c.tag_name)}function $r(e){if(!/^v\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(e))throw Error(`Unexpected app tag '${e}' (expected vX.Y.Z[-prerelease])`);return e.slice(1)}function Jn(e,t,r){let o=`shogo-agent-runtime-${t}.tar.gz`,n=`v${e}`,i=`${r.replace(/\/$/,"")}/${n}/${o}`;return{tarball:i,sha256:`${i}.sha256`,assetName:o}}async function Vn(e,t){let r=await fetch(e,{redirect:"follow"});if(!r.ok)throw Error(`Download failed: HTTP ${r.status} for ${e}`);if(!r.body)throw Error(`Download failed: empty body for ${e}`);await Bn(Hn.fromWeb(r.body),Ln(t))}async function qn(e){let t=await fetch(e,{redirect:"follow"});if(!t.ok)throw Error(`SHA256 sidecar fetch failed: HTTP ${t.status} for ${e}`);let o=(await t.text()).trim().split(/\s+/)[0];if(!/^[0-9a-f]{64}$/i.test(o))throw Error(`SHA256 sidecar at ${e} did not contain a 64-char hex digest`);return o.toLowerCase()}function Yn(e){let t=Ir(e);return Un("sha256").update(t).digest("hex")}async function Xn(e,t){await new Promise((r,o)=>{let n=T("tar",["-xzf",e,"-C",t],{stdio:["ignore","pipe","pipe"]}),i="";n.stderr.on("data",(s)=>{i+=s.toString()}),n.on("error",o),n.on("exit",(s)=>{if(s===0)r();else o(Error(`tar exited ${s}: ${i.trim()}`))})})}function zn(e,t){ft();let r=jn(t);if(!me(r))ut(r,{recursive:!0,mode:448});let o=`${t}.next`;if(me(o))pt(o,{force:!0});Tr(e,o);try{if(process.platform!=="win32"){let{chmodSync:n}=Oe("node:fs");n(o,493)}}catch{}if(me(t))pt(t,{force:!0});Tr(o,t)}async function Er(e={}){let t=e.logger??console,r=e.channel??"stable",o=e.baseUrl??process.env.SHOGO_RUNTIME_RELEASES_URL??Gn,n=e.target??xr(),i=e.version;if(!i)t.log(`[runtime install] Resolving latest ${r} version...`),i=await Kn(r,o),t.log(`[runtime install] Latest ${r} = ${i}`);let s=Me();if(s&&s.version===i&&s.target===n&&!e.force)return t.log(`[runtime install] ${i} (${n}) already installed at ${O} — pass --force to reinstall`),{version:i,target:n,binPath:O,source:s.source,sha256:s.sha256,channel:r};let a=Jn(i,n,o);t.log(`[runtime install] Downloading ${a.tarball}`);let l=Ee(Nn(),`shogo-runtime-install-${process.pid}-${Date.now()}`);ut(l,{recursive:!0});try{let c=Ee(l,a.assetName);await Vn(a.tarball,c),t.log("[runtime install] Verifying SHA-256...");let u=await qn(a.sha256),p=Yn(c);if(p!==u)throw Error(`SHA-256 mismatch for ${a.assetName}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
`,{mode:384}),t.log(`[runtime install] Installed agent-runtime ${i} (${n}) to ${O}`),{version:i,target:n,binPath:O,source:a.tarball,sha256:p,channel:r}}finally{try{pt(l,{recursive:!0,force:!0})}catch{}}}function Mr(){return{runtimeDir:re,runtimeBin:O,versionFile:oe}}async function dt(e={}){let t=await Er({channel:e.channel,version:e.version,baseUrl:e.baseUrl,force:e.force});console.log(),console.log(b.green("✓"),`agent-runtime ${b.bold(t.version)} installed (${t.target})`),console.log(` ${b.dim("path: ")} ${t.binPath}`),console.log(` ${b.dim("source: ")} ${t.source}`),console.log(` ${b.dim("sha256: ")} ${t.sha256}`),console.log(` ${b.dim("channel: ")} ${t.channel}`)}function _r(){let e=Me();if(!e){console.log(b.yellow("No agent-runtime installed.")),console.log(`Run ${b.cyan("shogo runtime install")} to download the latest.`),process.exitCode=1;return}console.log(`${b.bold("agent-runtime")} ${e.version}`),console.log(` ${b.dim("target: ")} ${e.target}`),console.log(` ${b.dim("channel: ")} ${e.channel}`),console.log(` ${b.dim("installed at:")} ${e.installedAt}`),console.log(` ${b.dim("source: ")} ${e.source}`)}function Ar(){let e=K(),t=Mr();if(!e){console.log(b.yellow("agent-runtime binary not found on this machine.")),console.log(),console.log(b.dim("Default install path:"),t.runtimeBin),console.log(),console.log(q()),process.exitCode=1;return}if(console.log(e.path),process.env.SHOGO_DEBUG||process.env.VERBOSE)console.log(b.dim(` (resolved via: ${e.source})`))}async function Cr(e={}){let t=Me(),r=e.channel??t?.channel??"stable";if(t)console.log(`${b.dim("current:")} ${t.version} (${t.target}, ${t.channel})`);else console.log(b.dim("No existing install — installing fresh..."));await dt({channel:r,baseUrl:e.baseUrl,force:!0})}import{existsSync as Zn,mkdirSync as Qn}from"node:fs";import{resolve as ei}from"node:path";import w from"picocolors";import{CloudFileTransport as ti}from"@shogo-ai/sdk/cloud-file-transport";async function Or(e,t){if(!e)throw Error("projectId is required");let r=D({apiKey:t.apiKey,cloudUrl:t.cloudUrl});yt(r.projectsDir);let o=ei(t.into??z(e,r.projectsDir));if(!Zn(o))Qn(o,{recursive:!0});let n=t.include?.split(",").map((a)=>a.trim()).filter(Boolean);if(console.log(w.bold(`
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
${
|
|
26
|
-
|
|
27
|
-
${
|
|
28
|
-
|
|
15
|
+
`)}`:" (nothing on stderr)";this.log.warn(`[WorkerRuntimeManager] runtime ${e.projectId} exited WITHOUT a stop request (code=${t}, signal=${r}, uptime ${Math.round((Date.now()-e.startedAt)/1000)}s, last activity ${this.activityDecision(e.projectId).countsText}) — last stderr:${p}`)}if(e.proc=null,e.graceTimer)clearTimeout(e.graceTimer),e.graceTimer=null;if(e.limitWatchdog)e.limitWatchdog.stop(),e.limitWatchdog=null;if(this.killViteWatch(e),e.status==="stopping"||this.stopped){e.status="stopped",e.pid=null,this.releasePort(e.agentPort),e.agentPort=0,e.apiServerPort=0;return}if(o){e.status="stopped",e.pid=null,this.releasePort(e.agentPort),e.agentPort=0,e.apiServerPort=0,this.runtimes.delete(e.projectId),this.notifyGone(e.projectId,{reason:"exited",code:t,signal:r});return}this.killProcessGroup(e,"SIGKILL"),this.killViteWatch(e),this.reapChildLedgerFor(e.projectId,e.spawnConfig.projectDir),e.pid=null,this.releasePort(e.agentPort),e.agentPort=0,e.apiServerPort=0;let i=Date.now(),s=i-e.lastFailureAt<=or;e.consecutiveFailures=s?e.consecutiveFailures+1:1,e.lastFailureAt=i,e.restarts+=1,e.lastError=`exited code=${t} signal=${r}`,e.restartTimestamps.push(i),e.restartTimestamps=e.restartTimestamps.filter((p)=>i-p<=nr);let a=e.consecutiveFailures>=rr,l=e.restartTimestamps.length>=pn;if(a||l){e.status="failed",e.breakerTrips+=1;let p=this.breakerCooldownMs(e.breakerTrips),u=a?`${e.consecutiveFailures} consecutive non-clean exits within ${Math.round(or/1000)}s`:`${e.restartTimestamps.length} non-clean exits within the last ${Math.round(nr/60000)}m (slow-bleed loop — gaps between crashes kept clearing the tight-loop window)`;if(e.lastError=`Circuit breaker tripped (trip #${e.breakerTrips}): ${u} (last: code=${t} signal=${r}). Most recent on macOS is jetsam OOM (signal=SIGKILL with code=null); the previous incarnation's vite/tsserver/preview-manager children were reaped to prevent further RSS growth. Will auto-retry in ~${Math.round(p/1000)}s; call resetFailure(${e.projectId}) to retry immediately, or stop(${e.projectId}) to give up.`,this.releasePort(e.agentPort),e.agentPort=0,e.apiServerPort=0,e.restartTimer)clearTimeout(e.restartTimer),e.restartTimer=null;if(e.idleTimer)clearTimeout(e.idleTimer),e.idleTimer=null;this.log.error(`[WorkerRuntimeManager] ${e.lastError}`),this.notifyGone(e.projectId,{reason:"failed",code:t,signal:r});return}let c=this.restartBackoffMs(e.restarts);if(e.status="restarting",this.log.warn(`[WorkerRuntimeManager] restarting ${e.projectId} in ${Math.round(c/1000)}s (restart #${e.restarts}, consecutive failures ${e.consecutiveFailures}/${rr})`),e.restartTimer)clearTimeout(e.restartTimer);e.restartTimer=setTimeout(()=>{if(e.restartTimer=null,e.status==="failed"||this.stopped)return;e.startPromise=this.doStart(e).then((p)=>(e.startPromise=null,p)).catch((p)=>(e.startPromise=null,this.log.error(`[WorkerRuntimeManager] restart of ${e.projectId} failed: ${p?.message??p}`),e))},c);try{e.restartTimer.unref?.()}catch{}}restartBackoffMs(e){let t=Math.min(cn*Math.pow(2,Math.max(0,e-1)),un),r=t*0.2*Math.random();return t+r}armIdleTimer(e){if(e.idleTimer)clearTimeout(e.idleTimer),e.idleTimer=null;let t=this.opts.idleMs??nn;if(!Number.isFinite(t)||t<=0)return;e.idleTimer=setTimeout(async()=>{e.idleTimer=null;let r=Date.now()-e.lastUsedAt;if(r<t){this.armIdleTimer(e);return}let o=await this.freshActivityDecision(e.projectId);if(this.runtimes.get(e.projectId)!==e||e.status!=="running"||e.idleTimer)return;if(o.dead){this.log.log(`[WorkerRuntimeManager] reaping dead ${e.projectId} (${ue(o)})`),this.stop(e.projectId,"SIGTERM","dead").catch((n)=>{this.log.warn(`[WorkerRuntimeManager] reap stop failed: ${n?.message??n}`)});return}if(o.busy){this.log.log(`[WorkerRuntimeManager] not idle-evicting ${e.projectId}: ${o.reason} (${ue(o)})`),this.touch(e.projectId);return}this.log.log(`[WorkerRuntimeManager] idle-evicting ${e.projectId} after ${Math.round(r/1000)}s`),this.stop(e.projectId,"SIGTERM","idle-evict").catch((n)=>{this.log.warn(`[WorkerRuntimeManager] idle stop failed: ${n?.message??n}`)})},t)}async hasActiveWork(e){let t=this.runtimes.get(e);if(!t)return!1;let r=await this.activeWork(t);return!!r&&r.turns+r.tasks+r.streams+r.children>0}async activeWork(e){if(e.status!=="running"||!e.agentPort)return null;try{let t=await fetch(`http://127.0.0.1:${e.agentPort}/pool/activity`,{headers:e.runtimeToken?{"x-runtime-token":e.runtimeToken}:{},signal:AbortSignal.timeout(an)});if(!t.ok)return null;let r=await t.json();if(!(typeof r.activeTurns==="number"||typeof r.activeTasks==="number"||typeof r.activeStreams==="number"||typeof r.activeChildren==="number"))return null;return{turns:r.activeTurns??0,tasks:r.activeTasks??0,streams:r.activeStreams??0,children:r.activeChildren??0}}catch{return null}}async sampleActivity(e){let t=Date.now(),r=await this.activeWork(e),o=e.activity,n=r?{counts:r,at:t,ok:!0}:{counts:o?.ok?o.counts:Ye,at:t,ok:!1,error:e.status!=="running"?`status=${e.status}`:e.agentPort?"no answer from /pool/activity":"no agent port",failingSince:o&&!o.ok?o.failingSince??t:t};return e.activity=n,n}async sampleAllActivity(){let e=[...this.runtimes.values()].filter((t)=>t.status==="running"&&!!t.agentPort);if(e.length===0)return;await Promise.all(e.map((t)=>this.sampleActivity(t).catch(()=>null)))}ensureActivitySampler(){if(this.activityTimer)return;let e=setInterval(()=>{this.sampleAllActivity()},Yt);e.unref?.(),this.activityTimer=e}activityDecision(e,t){let r=Date.now(),o=this.runtimes.get(e);if(!o)return Xe({now:r,startedAt:r,status:"stopped",processExited:!0});return Xe({now:r,startedAt:o.startedAt,status:o.status,sample:o.activity??null,terminalSessions:t?.terminalSessions,inflightCalls:t?.inflightCalls,processExited:this.processExited(o)})}processExited(e){if(e.status==="stopped"||e.status==="failed")return!0;if(e.proc)return!1;return e.status==="running"}async freshActivityDecision(e,t){let r=this.runtimes.get(e);if(r&&r.status==="running"&&r.agentPort){let o=r.activity;if(!o||Date.now()-o.at>qe)await this.sampleActivity(r).catch(()=>null)}return this.activityDecision(e,t)}async refreshActivity(e){let t=this.runtimes.get(e);if(!t||t.status!=="running"||!t.agentPort)return;let r=t.activity;if(r&&Date.now()-r.at<=qe)return;await this.sampleActivity(t).catch(()=>null)}async allocatePort(){let e=await this.ports.allocate({start:tn,end:rn,blockSize:rt,probeOffsets:[0,er]});return this.log.log(`[WorkerRuntimeManager] Allocated agent block ${e}-${e+rt-1}`),e}releasePort(e){if(!e)return;this.ports.releaseBlock(e,rt)}async logPortHolder(e){let t=e.agentPort;if(!t)return;let r=`[WorkerRuntimeManager] port ${t} (${e.projectId})`;for(let[o,n]of this.portHolderCommands(t)){let i=await Pn(o,n);if(!i)continue;let s=i.stdout.split(`
|
|
16
|
+
`).map((a)=>a.trim()).filter((a)=>a.includes(String(t)));if(s.length)this.log.error(`${r} is held by:
|
|
17
|
+
${s.join(`
|
|
18
|
+
`)}`);else this.log.error(`${r} has no listener now — the bind failure came from this runtime's own tree `+"(or the holder exited with it), not from a neighbouring process");return}this.log.warn(`${r}: no port-listing tool available, holder unknown`)}portHolderCommands(e){if(process.platform==="win32")return[["netstat",["-ano","-p","tcp"]]];return[["lsof",["-nP",`-iTCP:${e}`,"-sTCP:LISTEN"]],["ss",["-ltnp",`sport = :${e}`]]]}tcpProbe(e){return new Promise((t)=>{let r=!1,o=(i)=>{if(r)return;r=!0;try{n.destroy()}catch{}t(i)},n=Zo({host:"127.0.0.1",port:e});n.setTimeout(wn),n.once("connect",()=>o(!0)),n.once("error",()=>o(!1)),n.once("timeout",()=>o(!1))})}async waitForHealth(e,t){let{agentPort:r,proc:o}=e;if(!o)throw Error(`waitForHealth: slot ${e.projectId} has no spawned process`);let n=Date.now(),i=n+t,s=null,a=!1,l=0,c=0,p=0,u=0,m=n;while(Date.now()<i){if(u++,o.exitCode!==null||o.signalCode!=null||o.killed)throw Error(`agent-runtime exited (code=${o.exitCode}, signal=${o.signalCode}) before becoming healthy on port ${r}`);c++;let f=new AbortController,y=setTimeout(()=>f.abort(),1500);try{let d=await fetch(`http://127.0.0.1:${r}/health`,{method:"GET",signal:f.signal});if(clearTimeout(y),d.ok){this.log.log(`[WorkerRuntimeManager] /health ready for ${e.projectId} on port ${r} (HTTP ${d.status} after ${Date.now()-n}ms, ${u} iter, ${c} http)`);return}s=`HTTP /health returned ${d.status}`}catch(d){clearTimeout(y);let v=d?.name??"Error",ee=d?.code??d?.cause?.code;s=`HTTP /health failed: ${v}${ee?`(${ee})`:""}: ${d?.message??d}`}if(p++,a=await this.tcpProbe(r),a){l=Date.now();let d=Date.now()-e.lastStdoutAt;if(d>=ir)throw Error(`agent-runtime wedged on port ${r}: TCP listening but stdout silent for ${d}ms (> ${ir}ms window); ${c} /health attempts, last error: ${s??"n/a"}`)}let h=Date.now();if(h-m>=yn){let d=h-n,v=h-e.lastStdoutAt,ee=l>0?h-l:null;this.log.log(`[WorkerRuntimeManager] still waiting for /health on ${e.projectId} port ${r} (${(d/1000).toFixed(1)}s elapsed, tcpListening=${a}${ee!=null?`(${ee}ms ago)`:""}, lastStdout=${v}ms ago, ${c} http, ${p} tcp, lastError=${s??"n/a"})`),m=h}await new Promise((d)=>setTimeout(d,gn))}if(o.exitCode!==null||o.signalCode!=null||o.killed)throw Error(`agent-runtime exited (code=${o.exitCode}, signal=${o.signalCode}) before becoming healthy on port ${r}`);throw Error(`Timeout waiting for agent-runtime /health on port ${r} after ${u} iter (httpAttempts=${c}, tcpAttempts=${p}, tcpListening=${a}, lastError=${s??"n/a"})`)}async waitForExit(e,t){if(e.exitCode!==null||e.signalCode!=null||e.killed)return;await new Promise((r)=>{let o=setTimeout(()=>{try{e.kill("SIGKILL")}catch{}r()},t);e.once("exit",()=>{clearTimeout(o),r()})})}snapshot(e){return{projectId:e.projectId,status:e.status,agentPort:e.agentPort||void 0,apiServerPort:e.apiServerPort||void 0,pid:e.proc?.pid,startedAt:e.startedAt||void 0,lastUsedAt:e.lastUsedAt,restarts:e.restarts,lastError:e.lastError}}}function Pn(e,t){return new Promise((r)=>{q(e,t,{timeout:3000},(o,n)=>{if(o?.code==="ENOENT")return r(null);r({stdout:n??""})})})}import{hostname as st,platform as at,arch as lt}from"node:os";function z(){if(typeof __SHOGO_WORKER_VERSION__==="string"&&__SHOGO_WORKER_VERSION__.length>0)return`shogo-cli/${__SHOGO_WORKER_VERSION__}`;return"shogo-cli/unknown"}import{readFileSync as kn,writeFileSync as Rn,existsSync as Tn,chmodSync as $n}from"node:fs";import{randomUUID as In}from"node:crypto";function $e(){if(Tn(te)){let t=kn(te,"utf-8").trim();if(t)return t}B();let e=In();return Rn(te,e,{mode:384}),$n(te,384),e}class gr extends Error{code="TUNNEL_WS_HEADERS_UNSUPPORTED";constructor(){super("Tunnel WebSocket auth requires a runtime with WebSocket header support (Bun or Node >= 21). Current runtime does not support WebSocket constructor headers.");this.name="TunnelWebSocketHeaderSupportError"}}var pe=60,Ie=300,ct=3,xn=ct,En=1800000,_n=25000,dr=1000,mr=60000,hr=3;class ut{opts;log;pollTimer=null;ws=null;wsIdleTimer=null;heartbeatTimer=null;stopped=!1;currentPollInterval=pe;wsReconnectAttempt=0;lastHeartbeatError=null;consecutiveAuthFailures=0;consecutiveAuthSuccesses=0;serverPublishedWsUrl=null;lastInstanceId=null;activeAbortControllers=new Map;constructor(e){this.opts=e,this.log=e.logger??console}get instanceId(){return this.lastInstanceId}start(){if(!this.opts.apiKey){this.log.log("[WorkerTunnel] No API key set, skipping tunnel");return}this.stopped=!1,this.wsReconnectAttempt=0,this.currentPollInterval=pe,this.log.log("[WorkerTunnel] Starting heartbeat polling to Shogo Cloud..."),this.heartbeatLoop()}stop(){if(this.stopped=!0,this.pollTimer)clearTimeout(this.pollTimer),this.pollTimer=null;this.cleanupWs(),this.log.log("[WorkerTunnel] Tunnel stopped")}isConnected(){if(this.ws!==null&&this.ws.readyState===WebSocket.OPEN)return!0;return!this.stopped&&!!this.opts.apiKey&&this.lastHeartbeatError===null&&this.pollTimer!==null}getCloudUrl(){return this.opts.cloudUrl.replace(/\/$/,"")}deviceIdValue=null;get deviceId(){if(this.deviceIdValue!==null)return this.deviceIdValue;let e=(this.opts.deviceId??"").trim();if(!e)try{e=$e()}catch{e=""}return this.deviceIdValue=e,e}getWsBaseUrl(){let e=(this.opts.wsUrlOverride||process.env.SHOGO_TUNNEL_WS_URL||"").trim();if(e)return e.replace(/\/$/,"");if(this.serverPublishedWsUrl)return this.serverPublishedWsUrl.replace(/\/$/,"");return this.getCloudUrl().replace(/^http/,"ws")}buildWsUrl(){return`${this.getWsBaseUrl()}/api/instances/ws`}supportsWebSocketConstructorHeaders(e=globalThis){if(typeof e.Bun<"u"||typeof e.process?.versions?.bun==="string")return!0;let t=e.process?.versions?.node;if(t){if(parseInt(t.split(".")[0]??"0",10)>=21)return!0}return!1}createTunnelWebSocket(e,t,r=globalThis){if(!this.supportsWebSocketConstructorHeaders(r))throw new gr;return new WebSocket(e,t)}getReconnectDelayMs(){let e=Math.min(dr*Math.pow(2,this.wsReconnectAttempt),mr),t=e*0.2*Math.random();return e+t}async collectMetadata(){try{await this.opts.resolver.refresh?.()}catch{}let e={hostname:st(),os:at(),arch:lt(),uptime:process.uptime(),protocolVersion:hr,tunnelStatus:this.ws?.readyState===WebSocket.OPEN?"connected":"polling",kind:this.opts.kind??"cli-worker"};try{let t=this.opts.resolver.getActiveProjects();e.activeProjects=t.length,e.projects=t.map((r)=>{let o=this.opts.resolver.status(r);return{projectId:r,status:o?.status??"unknown",agentPort:o?.agentPort}})}catch{e.activeProjects=0}try{let t=await this.opts.resolver.machineSnapshot?.();if(t)e.machine=t}catch{}return e}async sendHeartbeat(){let e=this.getCloudUrl(),t=await this.collectMetadata(),r=st(),o=this.opts.name??r,n=await fetch(`${e}/api/instances/heartbeat`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":z(),"x-api-key":this.opts.apiKey,"x-shogo-kind":this.opts.kind??"cli-worker",...this.deviceId?{"x-shogo-device-id":this.deviceId}:{}},body:JSON.stringify({hostname:r,name:o,os:at(),arch:lt(),kind:this.opts.kind??"cli-worker",metadata:t})});if(!n.ok)throw Error(`Heartbeat failed: HTTP ${n.status}`);let i=await n.json();if(typeof i.instanceId==="string"&&i.instanceId.length>0)this.lastInstanceId=i.instanceId;if(typeof i.wsUrl==="string"&&i.wsUrl.length>0){if(i.wsUrl!==this.serverPublishedWsUrl)this.log.log(`[WorkerTunnel] Cloud advertised tunnel WS URL: ${i.wsUrl}`);this.serverPublishedWsUrl=i.wsUrl}return i}scheduleNextPoll(e){if(this.stopped)return;if(this.pollTimer)clearTimeout(this.pollTimer);let t=(e??this.currentPollInterval)*1000;this.pollTimer=setTimeout(()=>void this.heartbeatLoop(),t)}async heartbeatLoop(){if(this.stopped)return;if(this.ws&&this.ws.readyState===WebSocket.OPEN){this.scheduleNextPoll(this.currentPollInterval);return}try{let e=await this.sendHeartbeat(),t=e.nextPollIn||pe;if(this.consecutiveAuthFailures>=ct){if(this.consecutiveAuthSuccesses++,this.consecutiveAuthSuccesses<xn){this.currentPollInterval=Ie,this.scheduleNextPoll();return}}if(this.currentPollInterval=t,this.lastHeartbeatError)this.log.log("[WorkerTunnel] Heartbeat recovered"),this.lastHeartbeatError=null;if(this.consecutiveAuthFailures=0,this.consecutiveAuthSuccesses=0,e.wsRequested&&!this.ws){this.log.log("[WorkerTunnel] Cloud requested WebSocket — connecting..."),this.connectWs();return}}catch(e){let t=e?.message??String(e);if(/HTTP 40[13]\b/.test(t))this.consecutiveAuthFailures++,this.consecutiveAuthSuccesses=0;else this.consecutiveAuthFailures=0,this.consecutiveAuthSuccesses=0;if(t!==this.lastHeartbeatError)this.log.error(`[WorkerTunnel] Heartbeat error: ${t}`),this.lastHeartbeatError=t;if(this.consecutiveAuthFailures>=ct){let o=`tunnel saw ${this.consecutiveAuthFailures} consecutive auth failures from Shogo Cloud`;try{this.opts.onAuthRevoked?.(o)}catch(n){this.log.warn(`[WorkerTunnel] onAuthRevoked threw: ${n?.message??n}`)}if(this.currentPollInterval!==Ie)this.log.warn(`[WorkerTunnel] ${this.consecutiveAuthFailures} consecutive auth failures — `+`backing off to ${Ie}s. Re-authenticate with \`shogo login\`.`);this.currentPollInterval=Ie}else this.currentPollInterval=pe}this.scheduleNextPoll()}async handleRequest(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;this.resetWsIdleTimer();let t=new AbortController;this.activeAbortControllers.set(e.requestId,t);try{let r=await this.resolveLocalUrl(e.path,e.projectId);if(!r){let s=this.opts.resolver.describeRejection?await this.opts.resolver.describeRejection(e.path,e.projectId):{code:"NO_LOCAL_RUNTIME",message:`no local runtime available for path: ${e.path}`};this.sendFrame({type:"response",requestId:e.requestId,status:502,headers:{"content-type":"application/json"},body:JSON.stringify({code:s.code,message:s.message,path:e.path})});return}let o={...e.headers??{}};if(e.projectId&&(e.path.startsWith("/agent/")||e.path==="/agent")){let s=await this.opts.resolver.deriveRuntimeToken(e.projectId);if(s)o["x-runtime-token"]=s}let n={method:e.method,headers:o,signal:t.signal};if(e.body&&e.method!=="GET"&&e.method!=="HEAD")n.body=e.body;let i=await fetch(r,n);if(e.stream){let s=i.body?.getReader();if(!s){this.sendFrame({type:"stream-error",requestId:e.requestId,error:"No response body for stream"});return}let a=new TextDecoder;try{while(!0){let{done:l,value:c}=await s.read();if(l)break;if(this.ws?.readyState!==WebSocket.OPEN)break;this.sendFrame({type:"stream-chunk",requestId:e.requestId,data:a.decode(c,{stream:!0})})}if(this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-end",requestId:e.requestId})}catch(l){if(l?.name!=="AbortError"&&this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-error",requestId:e.requestId,error:l?.message??String(l)})}}else{let s=await i.text(),a={};i.headers.forEach((l,c)=>{a[c]=l}),this.sendFrame({type:"response",requestId:e.requestId,status:i.status,headers:a,body:s})}}catch(r){if(r?.name==="AbortError")return;if(this.ws?.readyState!==WebSocket.OPEN)return;let o=e.stream?{type:"stream-error",requestId:e.requestId,error:r?.message??String(r)}:{type:"response",requestId:e.requestId,status:502,body:JSON.stringify({error:r?.message??String(r)})};this.sendFrame(o)}finally{this.activeAbortControllers.delete(e.requestId)}}async resolveLocalUrl(e,t){return this.opts.resolver.resolveLocalUrl(e,t)}sendFrame(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;try{this.ws.send(JSON.stringify(e))}catch(t){this.log.warn(`[WorkerTunnel] Frame send failed: ${t?.message??t}`)}}resetWsIdleTimer(){if(this.wsIdleTimer)clearTimeout(this.wsIdleTimer);this.wsIdleTimer=setTimeout(()=>{if(this.ws&&this.ws.readyState===WebSocket.OPEN){this.log.log("[WorkerTunnel] WebSocket idle timeout — closing, returning to polling");try{this.ws.close(1000,"Idle timeout")}catch{}}},En)}startWsHeartbeat(){if(this.heartbeatTimer)clearInterval(this.heartbeatTimer);this.heartbeatTimer=setInterval(async()=>{if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;try{let e=await this.collectMetadata();this.sendFrame({type:"heartbeat",metadata:e})}catch{}},_n)}connectWs(){if(this.stopped||this.ws)return;let e=this.buildWsUrl(),t=st(),r=at(),o=lt(),n=this.opts.name??t;this.log.log(`[WorkerTunnel] Opening WebSocket to ${e} (hostname=${t})`);let i={headers:{Authorization:`Bearer ${this.opts.apiKey}`,"User-Agent":z(),"x-shogo-hostname":t,"x-shogo-name":n,"x-shogo-os":r,"x-shogo-arch":o,"x-shogo-kind":this.opts.kind??"cli-worker",...this.deviceId?{"x-shogo-device-id":this.deviceId}:{}}},s;try{s=this.createTunnelWebSocket(e,i)}catch(a){this.log.error(`[WorkerTunnel] WebSocket creation failed: ${a?.message??a}`),this.ws=null,this.scheduleNextPoll(5);return}this.ws=s,s.onopen=()=>{this.log.log("[WorkerTunnel] WebSocket connected — session active"),this.wsReconnectAttempt=0,this.startWsHeartbeat(),this.resetWsIdleTimer()},s.onmessage=(a)=>{let l;try{let c=typeof a.data==="string"?a.data:a.data.toString();l=JSON.parse(c)}catch{return}if(l.type==="ping"){this.sendFrame({type:"pong"}),this.resetWsIdleTimer();return}if(l.type==="cancel"){let c=this.activeAbortControllers.get(l.requestId);if(c)c.abort();return}if(l.type==="request"){this.handleRequest(l).catch((c)=>{this.log.error(`[WorkerTunnel] Error handling request: ${c?.message??c}`)});return}},s.onclose=(a)=>{if(this.log.log(`[WorkerTunnel] WebSocket closed: code=${a.code} reason=${a.reason||"none"}`),this.cleanupWs(),this.stopped)return;if(a.code===1000||a.code===4000)this.scheduleNextPoll(this.currentPollInterval);else{this.wsReconnectAttempt++;let l=this.getReconnectDelayMs();this.log.log(`[WorkerTunnel] Reconnecting in ${Math.round(l/1000)}s (attempt ${this.wsReconnectAttempt})`),this.scheduleNextPoll(Math.ceil(l/1000))}},s.onerror=(a)=>{this.log.error("[WorkerTunnel] WebSocket error:",a?.message??"unknown")}}cleanupWs(){if(this.heartbeatTimer)clearInterval(this.heartbeatTimer),this.heartbeatTimer=null;if(this.wsIdleTimer)clearTimeout(this.wsIdleTimer),this.wsIdleTimer=null;for(let[,e]of this.activeAbortControllers)try{e.abort()}catch{}this.activeAbortControllers.clear(),this.ws=null}_testing(){let e=this;return{sendHeartbeat:()=>e.sendHeartbeat(),heartbeatLoop:()=>e.heartbeatLoop(),connectWs:()=>e.connectWs(),cleanupWs:()=>e.cleanupWs(),handleRequest:(t)=>e.handleRequest(t),installFakeWs:(t)=>{e.ws=t},getCloudUrl:()=>e.getCloudUrl(),getWsBaseUrl:()=>e.getWsBaseUrl(),buildWsUrl:()=>e.buildWsUrl(),getReconnectDelayMs:()=>e.getReconnectDelayMs(),supportsWebSocketConstructorHeaders:(t)=>e.supportsWebSocketConstructorHeaders(t),createTunnelWebSocket:(t,r,o)=>e.createTunnelWebSocket(t,r,o),DEFAULT_POLL_INTERVAL_S:pe,BACKOFF_BASE_MS:dr,BACKOFF_MAX_MS:mr,TUNNEL_PROTOCOL_VERSION:hr,get currentPollInterval(){return e.currentPollInterval},set currentPollInterval(t){e.currentPollInterval=t},get wsReconnectAttempt(){return e.wsReconnectAttempt},set wsReconnectAttempt(t){e.wsReconnectAttempt=t},get ws(){return e.ws},get stopped(){return e.stopped},get serverPublishedWsUrl(){return e.serverPublishedWsUrl},set serverPublishedWsUrl(t){e.serverPublishedWsUrl=t}}}}async function fr(e){let t=D({name:e.name,workerDir:e.workerDir,apiKey:e.apiKey,cloudUrl:e.cloudUrl,port:e.port?parseInt(e.port,10):void 0,projectsDir:e.projectsDir}),r=Et(e.proxy);if(!e.foreground)return Mn({cfg:t,proxy:r,flags:e});let o=K({flag:e.runtimeBin});if(!o)console.error(g.red(Y({flag:e.runtimeBin}))),process.exit(1);if(Fe(process.env,r),e.debug){if(!await Ct(Mt({cloudUrl:t.cloudUrl,apiKey:t.apiKey,workerDir:t.workerDir,proxy:r})))process.exit(1)}let n=!e.noAutoPull,i=!e.noGit;if(console.log(g.bold(`
|
|
19
|
+
Shogo Worker — Starting`)),console.log(g.dim(" name ")+t.name),console.log(g.dim(" worker-dir ")+t.workerDir),console.log(g.dim(" cloud ")+t.cloudUrl),console.log(g.dim(" runtime ")+`${o.path} ${g.dim(`(via ${o.source})`)}`),console.log(g.dim(" auto-pull ")+(n?g.green("on")+g.dim(` → ${t.projectsDir}`):g.yellow("off"))),console.log(g.dim(" sync mode ")+(i?g.green("git")+g.dim(" (falls back to file transport if git is missing)"):g.yellow("files-only"))),e.project)console.log(g.dim(" project ")+e.project);if(r)console.log(g.dim(" proxy ")+`${r.url} ${g.dim(`(from ${r.source})`)}`);console.log("");let s=`${t.cloudUrl.replace(/\/+$/,"")}/api/ai/v1`,a={cloudUrl:t.cloudUrl,apiKey:t.apiKey,aiProxyUrl:s,aiProxyToken:t.apiKey,extraEnv:{SHOGO_PERMISSION_SCOPE:"cloud"}},l=new it({runtimeBin:e.runtimeBin,defaultSpawnConfig:a,autoPull:{enabled:n,projectsDir:t.projectsDir,watch:!0,useGit:i}});if(!l.resolveBinary())console.error(g.red(Y({flag:e.runtimeBin}))),process.exit(1);let c=new ut({apiKey:t.apiKey,cloudUrl:t.cloudUrl,name:t.name,kind:"cli-worker",resolver:l,onAuthRevoked:(m)=>{console.error(g.red(`✗ Cloud auth revoked: ${m}`)),console.error(g.dim(" Run `shogo login` to re-authenticate; this worker will keep polling at the auth-failure backoff until then."))}}),p=!1,u=async(m)=>{if(p)return;p=!0,console.log(g.dim(`
|
|
20
|
+
Received ${m} — shutting down...`));try{c.stop()}catch{}try{await l.stopAll()}catch(f){console.warn(g.yellow(`stopAll: ${f?.message??f}`))}process.exit(0)};process.once("SIGINT",()=>void u("SIGINT")),process.once("SIGTERM",()=>void u("SIGTERM")),process.once("SIGHUP",()=>void u("SIGHUP")),c.start(),console.log(g.green("✓ Worker running. Ctrl-C to stop.")),await new Promise(()=>{})}function Mn({cfg:e,proxy:t,flags:r}){let{entry:o,runner:n}=On(),i=Fe({...process.env,SHOGO_API_KEY:e.apiKey,SHOGO_CLOUD_URL:e.cloudUrl,SHOGO_INSTANCE_NAME:e.name,SHOGO_WORKER_DIR:e.workerDir,SHOGO_LOCAL_MODE:"true",PORT:String(e.port)},t),s=Cn(r),{pid:a}=$t({entry:o,runner:n,env:{...i,SHOGO_DETACHED_ARGS:s.join(" ")},cwd:e.workerDir,detach:!0,inheritStdio:!1});console.log(g.bold(`
|
|
21
|
+
Shogo Worker — Started`)),console.log(g.dim(" pid: ")+a),console.log(g.dim(" name: ")+e.name),console.log(g.dim(" logs: ")+"~/.shogo/logs/worker.log"),console.log(g.dim(" stop: ")+"shogo worker stop")}function Cn(e){let t=["worker","start","--foreground"];if(e.name)t.push("--name",e.name);if(e.workerDir)t.push("--worker-dir",e.workerDir);if(e.apiKey)t.push("--api-key",e.apiKey);if(e.cloudUrl)t.push("--cloud-url",e.cloudUrl);if(e.port)t.push("--port",e.port);if(e.proxy)t.push("--proxy",e.proxy);if(e.project)t.push("--project",e.project);if(e.runtimeBin)t.push("--runtime-bin",e.runtimeBin);if(e.debug)t.push("--debug");if(e.noAutoPull)t.push("--no-auto-pull");if(e.projectsDir)t.push("--projects-dir",e.projectsDir);if(e.noGit)t.push("--no-git");return t}function On(){let e=process.execPath,t=/\bbun(?:-[^/\\]*)?$/.test(e)||typeof globalThis.Bun<"u",r=process.argv[1];if(r&&An(r)){let n=/\.ts$/.test(r);return{entry:r,runner:t?"bun":n?"tsx":"node"}}return{entry:new URL("../../bin/shogo.mjs",import.meta.url).pathname,runner:t?"bun":"node"}}import yr from"picocolors";async function wr(){let{killedPid:e}=It("SIGTERM");if(e===null){console.log(yr.dim("No worker running."));return}console.log(yr.green(`✓ Worker stopped (pid=${e}).`))}import N from"picocolors";async function vr(){let e=ve();if(!e){console.log(N.yellow("● stopped")+N.dim(" (no pid file)"));return}if(!be(e)){console.log(N.red("● dead")+N.dim(` (stale pid ${e})`));return}let t=G();if(console.log(N.green("● running")+N.dim(` (pid ${e})`)),t.name)console.log(N.dim(" name: ")+t.name);if(t.cloudUrl)console.log(N.dim(" cloud: ")+t.cloudUrl);if(t.workerDir)console.log(N.dim(" dir: ")+t.workerDir)}W();import{existsSync as Dn,statSync as Wn}from"node:fs";import br from"picocolors";async function Sr(e){let t=e.err?fe:ge;if(!Dn(t)){console.log(br.dim("No logs yet."));return}let r=e.follow?["-F","-n","200",t]:["-n","200",t];if(T("tail",r,{stdio:"inherit"}).on("exit",(n)=>process.exit(n??0)),!e.follow){if(Wn(t).size===0)console.log(br.dim("(log file is empty)"))}}import x from"picocolors";W();import{hostname as Un,platform as Fn,arch as Ln}from"node:os";import J from"picocolors";class M extends Error{kind;constructor(e,t){super(e);this.kind=t;this.name="CloudLoginError"}}async function Pr(e){let t=e.cloudUrl.replace(/\/$/,""),r=e.client??"cli",o=e.deviceName??Un(),n=e.devicePlatform??`${Fn()}-${Ln()}`,i=e.appVersion??Hn(),s=e.log??((d)=>console.log(d)),a=e.fetchImpl??fetch,l=await a(`${t}/api/cli/login/start`,{method:"POST",headers:{"content-type":"application/json","user-agent":z()},body:JSON.stringify({deviceId:e.deviceId,deviceName:o,devicePlatform:n,deviceAppVersion:i,workspaceId:e.workspaceId,client:r}),signal:AbortSignal.timeout(1e4)}).catch((d)=>{throw new M(`Cannot reach Shogo Cloud at ${t}: ${d?.message??d}`,"transport")});if(!l.ok){let d=await l.text().catch(()=>"");throw new M(`Cloud rejected /api/cli/login/start (HTTP ${l.status}): ${d||"no body"}`,"transport")}let c=await l.json().catch(()=>({}));if(!c?.ok||!c.state||!c.authUrl)throw new M(`Cloud returned a malformed start response: ${c?.error??JSON.stringify(c)}`,"transport");let p=Nn(e.pollIntervalMs??c.pollIntervalMs),u=e.timeoutMs??c.expiresInMs,m=Date.now()+u;if(s(""),s(J.bold("Sign in to Shogo Cloud")),s(J.dim(" cloud: ")+t),s(J.dim(" device: ")+`${o} (${n})`),s(J.dim(" user code: ")+J.cyan(c.userCode)),s(""),s(" Open this URL in your browser to approve:"),s(" "+J.cyan(c.authUrl)),s(""),e.openBrowser!==!1)if(typeof e.openBrowser==="function")try{await e.openBrowser(c.authUrl)}catch{}else Bn(c.authUrl).catch(()=>{});s(J.dim("Waiting for approval..."));let f=e.installSignalHandlers??!0,y=()=>{h.abort()},h=new AbortController;if(e.abortSignal)if(e.abortSignal.aborted)h.abort();else e.abortSignal.addEventListener("abort",()=>h.abort(),{once:!0});if(f)process.once("SIGINT",y),process.once("SIGTERM",y);try{while(!0){if(h.signal.aborted)throw new M("Aborted by caller.","cancelled");if(Date.now()>=m)throw new M(`Timed out after ${Math.round(u/1000)}s waiting for approval.`,"timeout");let d=await a(`${t}/api/cli/login/poll?state=${encodeURIComponent(c.state)}`,{method:"GET",headers:{accept:"application/json","user-agent":z()},signal:AbortSignal.timeout(1e4)}).catch((v)=>(s(J.dim(` (poll error: ${v?.message??v} — retrying)`)),null));if(d&&d.ok){let v=await d.json().catch(()=>({}));if(v?.status==="approved"&&v.key)return{key:v.key,email:v.email??null,workspace:v.workspace??null,deviceId:v.deviceId??e.deviceId,workspaces:v.workspaces??null};if(v?.status==="denied")throw new M("Sign-in was denied in the browser.","denied");if(v?.status==="expired")throw new M("Sign-in request expired before it was approved.","expired")}await jn(p,h.signal)}}finally{if(f)process.removeListener("SIGINT",y),process.removeListener("SIGTERM",y)}}function Nn(e){if(!Number.isFinite(e)||e<=0)return 2000;return Math.min(Math.max(e,1000),1e4)}function jn(e,t){return new Promise((r,o)=>{if(t?.aborted)return o(new M("Aborted by caller.","cancelled"));let n=setTimeout(()=>{t?.removeEventListener("abort",i),r()},e),i=()=>{clearTimeout(n),o(new M("Aborted by caller.","cancelled"))};t?.addEventListener("abort",i,{once:!0})})}function Hn(){return z()}function Bn(e){return new Promise((t)=>{let r=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",o=process.platform==="win32"?["/c","start",'""',e]:[e];try{let n=T(r,o,{stdio:"ignore",detached:!0});n.on("error",()=>t()),n.unref(),t()}catch{t()}})}var Gn="https://studio.shogo.ai";async function kr(e){let t=G(),r=(e.cloudUrl||t.cloudUrl||Gn).replace(/\/$/,""),o=e.apiKey||process.env.SHOGO_API_KEY;if(o){await Kn({key:o,cloudUrl:r,cfg:t,name:e.name});return}let n=$e(),i;try{i=await Pr({cloudUrl:r,deviceId:n,deviceName:e.name,workspaceId:e.workspace,openBrowser:!e.noBrowser})}catch(s){if(s instanceof M){if(console.error(x.red(`✗ ${s.message}`)),s.kind==="transport")console.error(x.dim(" If your network blocks browsers, run with --api-key <key> instead."));process.exit(1)}throw s}if(t.apiKey=i.key,t.cloudUrl=r,e.name)t.name=e.name;if(ie(t),console.log(""),console.log(x.green("✓ Signed in to Shogo Cloud")),i.workspace)console.log(x.dim(" workspace: ")+i.workspace);if(i.email)console.log(x.dim(" email: ")+i.email);console.log(x.dim(" saved to: ")+"~/.shogo/config.json"),console.log(""),console.log(x.dim(" next: ")+"shogo worker start")}async function Kn({key:e,cloudUrl:t,cfg:r,name:o}){if(!/^shogo_sk_/.test(e))console.error(x.red('✗ API key should start with "shogo_sk_". Copy it verbatim from the API Keys page.')),process.exit(1);let n;try{let i=await fetch(`${t}/api/api-keys/validate`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({key:e}),signal:AbortSignal.timeout(1e4)});if(n=await i.json().catch(()=>({})),!i.ok||!n?.valid)console.error(x.red(`✗ ${n?.error||`Cloud rejected the key (HTTP ${i.status}).`}`)),process.exit(1)}catch(i){console.error(x.red(`✗ Cannot reach Shogo Cloud at ${t}: ${i?.message??i}`)),process.exit(1)}if(r.apiKey=e,r.cloudUrl=t,o)r.name=o;if(ie(r),console.log(x.green("✓ API key saved to ~/.shogo/config.json")),n.workspace?.name)console.log(x.dim(" workspace: ")+n.workspace.name);if(n.user?.email)console.log(x.dim(" email: ")+n.user.email);console.log(x.dim(" next: ")+"shogo worker start")}import de from"picocolors";async function Rr(){let e=G();if(Object.keys(e).length===0){console.log(de.dim("(no config — run `shogo login` or `shogo config set`)"));return}let t={...e,apiKey:e.apiKey?`***${e.apiKey.slice(-4)}`:void 0};console.log(de.dim(`file: ${H}`)),console.log(JSON.stringify(t,null,2))}async function Tr(e,t){let r=["apiKey","cloudUrl","name","workerDir","port"];if(!r.includes(e))console.error(de.red(`Unknown key: ${e}`)),console.error(de.dim(`Allowed: ${r.join(", ")}`)),process.exit(1);let o=G();o[e]=e==="port"?parseInt(t,10):t,ie(o),console.log(de.green(`✓ ${e} set`))}import S from"picocolors";W();import{createHash as Jn}from"node:crypto";import{createWriteStream as Vn,existsSync as me,mkdirSync as pt,readFileSync as xr,renameSync as $r,rmSync as dt,writeFileSync as qn}from"node:fs";import{tmpdir as Yn}from"node:os";import{dirname as Xn,join as xe}from"node:path";import{Readable as zn}from"node:stream";import{pipeline as Zn}from"node:stream/promises";var Qn="https://github.com/shogo-labs/shogo-ai/releases/download";function Er(){let e=process.platform,t=process.arch,r;if(e==="darwin")r="darwin";else if(e==="linux")r="linux";else if(e==="win32")r="windows";else throw Error(`Unsupported platform: ${e}`);let o;if(t==="arm64")o="arm64";else if(t==="x64")o="x64";else throw Error(`Unsupported arch: ${t} (need arm64 or x64)`);return`${r}-${o}`}function Ee(){if(!me(ne))return null;try{let e=xr(ne,"utf-8");return JSON.parse(e)}catch{return null}}async function ei(e,t){let r=t.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/releases\/download/);if(!r)throw Error(`Cannot auto-resolve latest version from non-GitHub baseUrl '${t}'. Pass --version explicitly.`);let o=r[1],n=r[2];if(e==="stable"){let p=`https://api.github.com/repos/${o}/${n}/releases/latest`,u=await fetch(p,{headers:{Accept:"application/vnd.github+json"}});if(!u.ok)throw Error(`GitHub API ${u.status} for ${p}`);let m=await u.json();if(!m.tag_name)throw Error("GitHub API did not return tag_name");return Ir(m.tag_name)}let i=`https://api.github.com/repos/${o}/${n}/releases?per_page=30`,s=await fetch(i,{headers:{Accept:"application/vnd.github+json"}});if(!s.ok)throw Error(`GitHub API ${s.status} for ${i}`);let a=await s.json(),l=e==="beta"?"-beta":"-nightly",c=a.find((p)=>p.prerelease&&/^v\d+\.\d+\.\d+-/.test(p.tag_name)&&p.tag_name.includes(l));if(!c)throw Error(`No ${e} runtime release found`);return Ir(c.tag_name)}function Ir(e){if(!/^v\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(e))throw Error(`Unexpected app tag '${e}' (expected vX.Y.Z[-prerelease])`);return e.slice(1)}function ti(e,t,r){let o=`shogo-agent-runtime-${t}.tar.gz`,n=`v${e}`,i=`${r.replace(/\/$/,"")}/${n}/${o}`;return{tarball:i,sha256:`${i}.sha256`,assetName:o}}async function ri(e,t){let r=await fetch(e,{redirect:"follow"});if(!r.ok)throw Error(`Download failed: HTTP ${r.status} for ${e}`);if(!r.body)throw Error(`Download failed: empty body for ${e}`);await Zn(zn.fromWeb(r.body),Vn(t))}async function oi(e){let t=await fetch(e,{redirect:"follow"});if(!t.ok)throw Error(`SHA256 sidecar fetch failed: HTTP ${t.status} for ${e}`);let o=(await t.text()).trim().split(/\s+/)[0];if(!/^[0-9a-f]{64}$/i.test(o))throw Error(`SHA256 sidecar at ${e} did not contain a 64-char hex digest`);return o.toLowerCase()}function ni(e){let t=xr(e);return Jn("sha256").update(t).digest("hex")}async function ii(e,t){await new Promise((r,o)=>{let n=T("tar",["-xzf",e,"-C",t],{stdio:["ignore","pipe","pipe"]}),i="";n.stderr.on("data",(s)=>{i+=s.toString()}),n.on("error",o),n.on("exit",(s)=>{if(s===0)r();else o(Error(`tar exited ${s}: ${i.trim()}`))})})}function si(e,t){yt();let r=Xn(t);if(!me(r))pt(r,{recursive:!0,mode:448});let o=`${t}.next`;if(me(o))dt(o,{force:!0});$r(e,o);try{if(process.platform!=="win32"){let{chmodSync:n}=Ce("node:fs");n(o,493)}}catch{}if(me(t))dt(t,{force:!0});$r(o,t)}async function _r(e={}){let t=e.logger??console,r=e.channel??"stable",o=e.baseUrl??process.env.SHOGO_RUNTIME_RELEASES_URL??Qn,n=e.target??Er(),i=e.version;if(!i)t.log(`[runtime install] Resolving latest ${r} version...`),i=await ei(r,o),t.log(`[runtime install] Latest ${r} = ${i}`);let s=Ee();if(s&&s.version===i&&s.target===n&&!e.force)return t.log(`[runtime install] ${i} (${n}) already installed at ${O} — pass --force to reinstall`),{version:i,target:n,binPath:O,source:s.source,sha256:s.sha256,channel:r};let a=ti(i,n,o);t.log(`[runtime install] Downloading ${a.tarball}`);let l=xe(Yn(),`shogo-runtime-install-${process.pid}-${Date.now()}`);pt(l,{recursive:!0});try{let c=xe(l,a.assetName);await ri(a.tarball,c),t.log("[runtime install] Verifying SHA-256...");let p=await oi(a.sha256),u=ni(c);if(u!==p)throw Error(`SHA-256 mismatch for ${a.assetName}
|
|
22
|
+
expected: ${p}
|
|
23
|
+
actual: ${u}`);let m=xe(l,"extract");pt(m,{recursive:!0}),await ii(c,m);let f=xe(m,"agent-runtime");if(!me(f))throw Error(`Tarball ${a.assetName} did not contain ./agent-runtime`);si(f,O);let y={version:i,target:n,installedAt:new Date().toISOString(),channel:r,source:a.tarball,sha256:u};return qn(ne,JSON.stringify(y,null,2)+`
|
|
24
|
+
`,{mode:384}),t.log(`[runtime install] Installed agent-runtime ${i} (${n}) to ${O}`),{version:i,target:n,binPath:O,source:a.tarball,sha256:u,channel:r}}finally{try{dt(l,{recursive:!0,force:!0})}catch{}}}function Ar(){return{runtimeDir:oe,runtimeBin:O,versionFile:ne}}async function mt(e={}){let t=await _r({channel:e.channel,version:e.version,baseUrl:e.baseUrl,force:e.force});console.log(),console.log(S.green("✓"),`agent-runtime ${S.bold(t.version)} installed (${t.target})`),console.log(` ${S.dim("path: ")} ${t.binPath}`),console.log(` ${S.dim("source: ")} ${t.source}`),console.log(` ${S.dim("sha256: ")} ${t.sha256}`),console.log(` ${S.dim("channel: ")} ${t.channel}`)}function Mr(){let e=Ee();if(!e){console.log(S.yellow("No agent-runtime installed.")),console.log(`Run ${S.cyan("shogo runtime install")} to download the latest.`),process.exitCode=1;return}console.log(`${S.bold("agent-runtime")} ${e.version}`),console.log(` ${S.dim("target: ")} ${e.target}`),console.log(` ${S.dim("channel: ")} ${e.channel}`),console.log(` ${S.dim("installed at:")} ${e.installedAt}`),console.log(` ${S.dim("source: ")} ${e.source}`)}function Cr(){let e=K(),t=Ar();if(!e){console.log(S.yellow("agent-runtime binary not found on this machine.")),console.log(),console.log(S.dim("Default install path:"),t.runtimeBin),console.log(),console.log(Y()),process.exitCode=1;return}if(console.log(e.path),process.env.SHOGO_DEBUG||process.env.VERBOSE)console.log(S.dim(` (resolved via: ${e.source})`))}async function Or(e={}){let t=Ee(),r=e.channel??t?.channel??"stable";if(t)console.log(`${S.dim("current:")} ${t.version} (${t.target}, ${t.channel})`);else console.log(S.dim("No existing install — installing fresh..."));await mt({channel:r,baseUrl:e.baseUrl,force:!0})}import{existsSync as ai,mkdirSync as li}from"node:fs";import{resolve as ci}from"node:path";import w from"picocolors";import{CloudFileTransport as ui}from"@shogo-ai/sdk/cloud-file-transport";async function Dr(e,t){if(!e)throw Error("projectId is required");let r=D({apiKey:t.apiKey,cloudUrl:t.cloudUrl});wt(r.projectsDir);let o=ci(t.into??Z(e,r.projectsDir));if(!ai(o))li(o,{recursive:!0});let n=t.include?.split(",").map((a)=>a.trim()).filter(Boolean);if(console.log(w.bold(`
|
|
25
|
+
shogo project pull ${w.cyan(e)}`)),console.log(w.dim(" cloud ")+r.cloudUrl),console.log(w.dim(" into ")+o),n?.length)console.log(w.dim(" include ")+n.join(", "));console.log("");let i=new ui({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,include:n,onProgress:pi()}),s=await i.downloadAll();if(di("Pull",s),t.watch){console.log(w.bold(`
|
|
26
|
+
Watching for local changes...`));let a=new ce({rootDir:o,transport:i,onFlush:({uploaded:p,errors:u})=>{let m=p.length===1?p[0]:`${p.length} files`,f=u>0?w.red(` (${u} errors)`):"";console.log(w.dim(` ↑ ${m}${f}`))}});a.start();let l=!1,c=async(p)=>{if(l)return;l=!0,console.log(w.dim(`
|
|
27
|
+
Received ${p} — flushing pending uploads...`));try{await a.stop()}catch(u){console.warn(w.yellow(`watcher.stop: ${u?.message??u}`))}process.exit(0)};process.once("SIGINT",()=>void c("SIGINT")),process.once("SIGTERM",()=>void c("SIGTERM")),process.once("SIGHUP",()=>void c("SIGHUP")),await new Promise(()=>{})}}function pi(){return(e)=>{let t=e.kind==="download"?"↓":e.kind==="upload"?"↑":e.kind==="delete"?"✗":"·",r=e.total>0?` ${e.index+1}/${e.total}`:"",o=e.bytes!=null?w.dim(` (${mi(e.bytes)})`):"";console.log(w.dim(` ${t}${r} ${e.path}${o}`))}}function di(e,t){let r=t.errors.length===0,o=r?w.green(`✓ ${e} complete`):w.red(`✗ ${e} completed with errors`);if(console.log(`
|
|
28
|
+
${o}`),console.log(w.dim(" downloaded: ")+t.downloaded),t.uploaded)console.log(w.dim(" uploaded: ")+t.uploaded);if(t.deleted)console.log(w.dim(" deleted: ")+t.deleted);if(t.skipped)console.log(w.dim(" skipped: ")+t.skipped);if(!r){console.log(w.dim(" errors: ")+t.errors.length);for(let n of t.errors.slice(0,5))console.log(w.dim(" ")+w.red(`${n.path}: ${n.message}`));if(t.errors.length>5)console.log(w.dim(` ... and ${t.errors.length-5} more`))}}function mi(e){if(e<1024)return`${e}B`;if(e<1048576)return`${(e/1024).toFixed(1)}KB`;return`${(e/1024/1024).toFixed(2)}MB`}import{existsSync as hi}from"node:fs";import{resolve as gi}from"node:path";import P from"picocolors";import{CloudFileTransport as fi}from"@shogo-ai/sdk/cloud-file-transport";async function Wr(e,t){if(!e)throw Error("projectId is required");let r=D({apiKey:t.apiKey,cloudUrl:t.cloudUrl}),o=gi(t.from??Z(e,r.projectsDir));if(!hi(o))throw Error(`Source directory does not exist: ${o}`);let n=t.include?.split(",").map((a)=>a.trim()).filter(Boolean);if(console.log(P.bold(`
|
|
29
|
+
shogo project push ${P.cyan(e)}`)),console.log(P.dim(" cloud ")+r.cloudUrl),console.log(P.dim(" from ")+o),n?.length)console.log(P.dim(" include ")+n.join(", "));if(t.deleteRemote)console.log(P.yellow(" --delete-remote: remote files not present locally will be DELETED"));console.log("");let s=await new fi({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,include:n,onProgress:(a)=>{let l=a.kind==="upload"?"↑":a.kind==="delete"?"✗":"·",c=a.total>0?` ${a.index+1}/${a.total}`:"",p=a.bytes!=null?P.dim(` (${wi(a.bytes)})`):"";console.log(P.dim(` ${l}${c} ${a.path}${p}`))}}).uploadAll({deleteRemote:t.deleteRemote});yi("Push",s)}function yi(e,t){let r=t.errors.length===0,o=r?P.green(`✓ ${e} complete`):P.red(`✗ ${e} completed with errors`);if(console.log(`
|
|
30
|
+
${o}`),console.log(P.dim(" uploaded: ")+t.uploaded),t.deleted)console.log(P.dim(" deleted: ")+t.deleted);if(!r){console.log(P.dim(" errors: ")+t.errors.length);for(let n of t.errors.slice(0,5))console.log(P.dim(" ")+P.red(`${n.path}: ${n.message}`));if(t.errors.length>5)console.log(P.dim(` ... and ${t.errors.length-5} more`))}}function wi(e){if(e<1024)return`${e}B`;if(e<1048576)return`${(e/1024).toFixed(1)}KB`;return`${(e/1024/1024).toFixed(2)}MB`}import{existsSync as vi}from"node:fs";import{resolve as bi}from"node:path";import F from"picocolors";Re();async function Ur(e,t){if(!e)throw Error("projectId is required");let r=D({apiKey:t.apiKey,cloudUrl:t.cloudUrl}),o=bi(t.into??Z(e,r.projectsDir));if(!vi(o))throw Error(`Local project dir does not exist: ${o} — run \`shogo project pull\` first`);if(!ke(o))throw Error(`${o} is not a git repo. \`shogo project checkout\` requires the git sync path; run \`shogo project pull\` after installing git, or use \`shogo project pull --include\` for file-only restore.`);if(console.log(F.bold(`
|
|
31
|
+
shogo project checkout ${F.cyan(e)}`)),console.log(F.dim(" cloud ")+r.cloudUrl),console.log(F.dim(" local ")+o),t.at)console.log(F.dim(" at ")+t.at);if(console.log(""),t.unshallow)console.log(F.dim("Unshallowing repo (this may take a moment)...")),await Ve({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o});if(!t.at){let i=await Pe({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o});console.log(F.green(`✓ Reset to ${i.commitSha.slice(0,8)} (remote HEAD)`));return}let n;try{n=(await I(["rev-parse","--verify",`${t.at}^{commit}`],{cwd:o})).stdout.trim()}catch{n=await Si(r.cloudUrl,r.apiKey,e,t.at),console.log(F.dim(` resolved checkpoint "${t.at}" → ${n.slice(0,8)}`))}try{await Pe({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,branch:n})}catch{if(!t.unshallow)console.log(F.yellow(" fetch failed; unshallowing and retrying...")),await Ve({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o}),await Pe({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,branch:n});else throw Error(`Cannot reach commit ${n} in local clone`)}await I(["reset","--hard",n],{cwd:o}),console.log(F.green(`✓ Checked out ${n.slice(0,8)}`))}async function Si(e,t,r,o){let n=`${e.replace(/\/+$/,"")}/api/projects/${encodeURIComponent(r)}/checkpoints?limit=100`,i=await fetch(n,{headers:{Authorization:`Bearer ${t}`}});if(!i.ok)throw Error(`Failed to list checkpoints: HTTP ${i.status}`);let s=await i.json(),a=o.toLowerCase(),l=s.checkpoints.find((c)=>{if(c.name&&c.name.toLowerCase()===a)return!0;if(c.commitSha.startsWith(o))return!0;if(c.commitMessage&&c.commitMessage.toLowerCase().includes(a))return!0;return!1});if(!l)throw Error(`No checkpoint matches "${o}" (searched ${s.checkpoints.length} most recent)`);return l.commitSha}import{createInterface as $i}from"node:readline";import b from"picocolors";W();import{existsSync as Q,copyFileSync as Fr}from"node:fs";import C from"node:path";import{homedir as Lr,platform as Nr}from"node:os";function jr(e,t,r){let n=We(e,["-e",r],{env:{...process.env,DBP:t},encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:5000}).trim();if(!n)throw Error(`bun script returned empty output for ${t}`);return JSON.parse(n)}function _e(e,t){if(!Q(t))return[];return jr(e,t,`
|
|
29
32
|
import { Database } from 'bun:sqlite';
|
|
30
33
|
try {
|
|
31
34
|
const db = new Database(process.env.DBP, { readonly: true });
|
|
@@ -39,7 +42,7 @@ shogo project checkout ${L.cyan(e)}`)),console.log(L.dim(" cloud ")+r.cloudUrl
|
|
|
39
42
|
console.error(String(e?.stack || e));
|
|
40
43
|
process.exit(2);
|
|
41
44
|
}
|
|
42
|
-
`)}function
|
|
45
|
+
`)}function Pi(e){if(!Q(e))throw Error(`Database does not exist at ${e} — refusing to back up nothing`);let t=C.dirname(e),r=C.basename(e),o=new Date().toISOString().replace(/[:.]/g,"-"),n=C.join(t,`${r}.bak-${o}`);Fr(e,n);for(let i of["-wal","-shm"]){let s=`${e}${i}`;if(Q(s))Fr(s,`${n}${i}`)}return n}function ki(e,t,r){if(r.length===0)return 0;if(!Q(t))throw Error(`Cannot repair: database does not exist at ${t}`);let n=`
|
|
43
46
|
import { Database } from 'bun:sqlite';
|
|
44
47
|
const names = ${JSON.stringify(r)};
|
|
45
48
|
if (!Array.isArray(names) || names.some(n => typeof n !== 'string')) {
|
|
@@ -58,8 +61,8 @@ shogo project checkout ${L.cyan(e)}`)),console.log(L.dim(" cloud ")+r.cloudUrl
|
|
|
58
61
|
console.error(String(e?.stack || e));
|
|
59
62
|
process.exit(3);
|
|
60
63
|
}
|
|
61
|
-
`;return
|
|
62
|
-
`)[0]?.trim();if(console.log(` ${
|
|
63
|
-
`+" (e.g. /Applications/Shogo.app/Contents/Resources/bun/bun on macOS).");console.log(
|
|
64
|
-
→ Relaunch the Shogo app to finish applying migrations.`))}else{if(console.log(
|
|
65
|
-
(use --help for usage)`));
|
|
64
|
+
`;return jr(e,t,n).deleted}function Hr(e){let t=e.log??(()=>{}),{bunPath:r,dbPath:o}=e;if(!Q(o))return{status:"no-database",detected:[],cleared:[],remaining:[],message:`No database found at ${o}. Nothing to repair — launch Shogo once to create it.`};let n=_e(r,o);if(n.length===0)return{status:"healthy",detected:[],cleared:[],remaining:[],message:"No failed migrations detected — the local database looks healthy."};t(`Found ${n.length} failed migration(s): ${n.map((u)=>u.name).join(", ")}`);let i;if(!e.skipBackup)i=Pi(o),t(`Backed up database to ${i}`);let s=n.map((u)=>u.name),a=ki(r,o,s);t(`Cleared ${a} failed migration row(s).`);let l=_e(r,o),c=l.length===0?"repaired":"failed",p=c==="repaired"?"Cleared the failed migration record. Relaunch Shogo to re-apply migrations cleanly.":`Repair incomplete — ${l.length} migration(s) still failed: ${l.map((u)=>u.name).join(", ")}.`;return{status:c,detected:n,backupPath:i,cleared:s.slice(0,a),remaining:l,message:p}}function Ri(){let e=Lr(),t=Nr(),r;if(t==="darwin")r=C.join(e,"Library","Application Support");else if(t==="win32")r=process.env.APPDATA??C.join(e,"AppData","Roaming");else r=process.env.XDG_CONFIG_HOME??C.join(e,".config");return C.join(r,"Shogo","data")}function Br(){return C.join(Ri(),"shogo.db")}function Ti(){let e=Nr(),t=e==="win32"?"bun.exe":"bun",r=Lr(),o=[];if(e==="darwin")o.push(C.join("/Applications","Shogo.app","Contents","Resources","bun",t),C.join(r,"Applications","Shogo.app","Contents","Resources","bun",t));else if(e==="linux")o.push(C.join("/opt","Shogo","resources","bun",t),C.join("/usr","lib","shogo","resources","bun",t));return o}function ht(e){try{return We(e,["--version"],{stdio:"ignore",timeout:5000}),!0}catch{return!1}}function Gr(e){if(e)return ht(e)?e:null;if(process.versions.bun&&process.execPath)return process.execPath;for(let t of Ti())if(Q(t)&&ht(t))return t;if(ht("bun"))return"bun";return null}function Ii(e){for(let t of e){let r=Number.isFinite(t.startedAt)?new Date(t.startedAt).toISOString():"unknown time",o=(t.errorExcerpt??"").split(`
|
|
65
|
+
`)[0]?.trim();if(console.log(` ${b.yellow("•")} ${b.bold(t.name)} ${b.dim(`(attempted ${r})`)}`),o)console.log(` ${b.dim(o)}`)}}async function xi(e){if(!process.stdin.isTTY)return console.log(b.dim("(stdin is not a TTY — re-run with --yes to repair non-interactively)")),!1;let t=$i({input:process.stdin,output:process.stdout});try{let r=await new Promise((o)=>t.question(e,o));return/^y(es)?$/i.test(r.trim())}finally{t.close()}}async function Kr(e={}){let t=e.db??Br(),r=Gr(e.bun);if(!r)throw Error("Could not find a usable `bun` binary (needed to inspect the SQLite database).\n"+" • Install Bun (https://bun.sh) so `bun` is on your PATH, or\n"+` • pass --bun <path> pointing at the bun shipped inside the Shogo app
|
|
66
|
+
`+" (e.g. /Applications/Shogo.app/Contents/Resources/bun/bun on macOS).");console.log(b.dim(`Database: ${t}`)),console.log(b.dim(`Bun: ${r}`)),console.log();let o=_e(r,t);if(o.length===0){console.log(b.green("✓ No failed migrations detected — your local database looks healthy."));return}if(console.log(b.red(`✗ Found ${o.length} failed migration(s):`)),Ii(o),console.log(),e.check){console.log(b.dim("Run `shogo doctor` (without --check) to back up the database and clear these records.")),process.exitCode=1;return}if(!e.yes){let i=e.backup===!1?b.red("This will NOT create a backup (--no-backup)."):"Your database will be backed up to a .bak-<timestamp> file first.";if(console.log(i),!await xi(b.bold("Clear these failed migration records and repair? [y/N] "))){console.log(b.dim("Aborted — no changes made.")),process.exitCode=1;return}console.log()}let n=Hr({bunPath:r,dbPath:t,skipBackup:e.backup===!1,log:(i)=>console.log(b.dim(` ${i}`))});if(console.log(),n.status==="repaired"){if(console.log(b.green(`✓ ${n.message}`)),n.backupPath)console.log(b.dim(` Backup: ${n.backupPath}`));console.log(b.bold(`
|
|
67
|
+
→ Relaunch the Shogo app to finish applying migrations.`))}else{if(console.log(b.red(`✗ ${n.message}`)),n.backupPath)console.log(b.dim(` Backup: ${n.backupPath}`));process.exitCode=1}}W();import{resolve as Ei}from"node:path";import Jr from"picocolors";function _i(e){let{flags:t,config:r,runtime:o}=e,n=t.cwd?Ei(t.cwd):process.cwd(),i=["interactive"];if(t.print!==void 0)i.push("-p",t.print);if(t.noTui)i.push("--no-tui");let s=r.cloudUrl.replace(/\/$/,""),a={...e.baseEnv??process.env,SHOGO_INTERACTIVE:"1",SHOGO_INTERACTIVE_CWD:n,PROJECT_DIR:n,WORKSPACE_DIR:n,SHOGO_API_URL:s,SHOGO_CLOUD_URL:s,SHOGO_API_KEY:r.apiKey,AI_PROXY_URL:`${s}/api/ai/v1`,AI_PROXY_TOKEN:r.apiKey,NODE_ENV:"production"};if(t.model)a.SHOGO_MODEL=t.model;if(t.print!==void 0)a.SHOGO_PRINT_PROMPT=t.print;return{bin:o.path,args:i,env:a,cwd:n}}async function Vr(e={}){let t=D({apiKey:e.apiKey,cloudUrl:e.cloudUrl}),r=K({flag:e.runtimeBin});if(!r)console.error(Jr.red(Y({flag:e.runtimeBin}))),process.exit(1);let o=_i({flags:e,config:{apiKey:t.apiKey,cloudUrl:t.cloudUrl},runtime:r}),n=await new Promise((i)=>{let s=T(o.bin,o.args,{cwd:o.cwd,env:o.env,stdio:"inherit"});s.on("error",(a)=>{console.error(Jr.red(`✗ Failed to launch agent-runtime: ${a.message}`)),i(1)}),s.on("exit",(a)=>i(a??0))});process.exit(n)}var Mi="0.1.0",L=new Ai;L.name("shogo").description("Shogo Cloud Agent Worker — run Shogo agents on your own machine.").version(Mi);L.command("login").description("Pair this machine with Shogo Cloud (browser device flow, or --api-key for CI)").option("--api-key <key>","CI escape hatch — skip the browser flow and use a key directly").option("--cloud-url <url>","Shogo Cloud URL (default: https://studio.shogo.ai)").option("--name <name>","Device label shown in the dashboard (default: hostname)").option("--workspace <id>","Pre-select a workspace on the bridge picker").option("--no-browser","Do not auto-open the browser; print the URL instead").action((e)=>k(()=>kr(e)));var Ae=L.command("worker").description("Manage the local worker process");Ae.command("start").description("Start the worker and pair with Shogo Cloud").option("--name <name>","Instance name shown in the dashboard (default: hostname)").option("--worker-dir <path>","Working directory for the worker (default: $PWD)").option("--api-key <key>","API key (overrides config/env)").option("--cloud-url <url>","Shogo Cloud URL").option("--port <port>","Local HTTP port for the embedded API").option("--proxy <url>","HTTPS proxy (overrides HTTPS_PROXY env)").option("--project <id>","Pin to a single project (default: multi-project on demand)").option("--runtime-bin <path>","Override the agent-runtime binary path").option("--debug","Run preflight checks before starting").option("--foreground","Run in foreground (don't detach)").option("--no-auto-pull","Disable auto-clone of project workspaces on first request").option("--projects-dir <path>","Root directory for cloned project workspaces (default: ~/.shogo/projects)").option("--no-git","Force the file-transport sync path even when git is available").action((e)=>k(()=>fr(e)));Ae.command("stop").description("Stop the running worker").action(()=>k(wr));Ae.command("status").description("Show worker status").action(()=>k(vr));Ae.command("logs").description("Tail worker logs from ~/.shogo/logs/worker.log").option("-f, --follow","Follow the log (tail -F)").option("--err","Show stderr log instead").action((e)=>k(()=>Sr(e)));var Me=L.command("runtime").description("Install / inspect the local agent-runtime binary");Me.command("install").description("Download + verify the agent-runtime tarball into ~/.shogo/runtime/").option("--channel <channel>","stable | beta | nightly (default: stable)").option("--version <version>","install a specific version (e.g. 0.1.0)").option("--base-url <url>","override release base URL (default: GitHub Releases)").option("--force","reinstall even if the same version is already on disk").action((e)=>k(()=>mt(e)));Me.command("version").description("Print the installed agent-runtime version").action(()=>k(()=>Mr()));Me.command("where").description("Print the resolved agent-runtime binary path").action(()=>k(()=>Cr()));Me.command("update").description("Update agent-runtime to the latest in its channel").option("--channel <channel>","override channel (defaults to whatever is installed)").option("--base-url <url>","override release base URL").action((e)=>k(()=>Or(e)));var Yr=L.command("config").description("Inspect or modify ~/.shogo/config.json");Yr.command("show").description("Print current config (API key masked)").action(()=>k(Rr));Yr.command("set <key> <value>").description("Set apiKey / cloudUrl / name / workerDir / port / projectsDir").action((e,t)=>k(()=>Tr(e,t)));var gt=L.command("project").description("Clone/sync project workspaces between Shogo Cloud and this machine");gt.command("pull <projectId>").description("Clone a project from Shogo Cloud into ~/.shogo/projects/<projectId>/").option("--into <dir>","Destination directory (default: ~/.shogo/projects/<projectId>)").option("--watch","After pull, watch the local dir and push edits back to cloud").option("--include <patterns>",'Comma-separated glob patterns (e.g. "src/**,*.md")').option("--api-key <key>","Override API key for this run").option("--cloud-url <url>","Override Shogo Cloud URL for this run").action((e,t)=>k(()=>Dr(e,t)));gt.command("push <projectId>").description("Upload local workspace edits back to Shogo Cloud").option("--from <dir>","Source directory (default: ~/.shogo/projects/<projectId>)").option("--delete-remote","Mirror local deletions to cloud (DESTRUCTIVE)").option("--include <patterns>","Comma-separated glob patterns").option("--api-key <key>","Override API key for this run").option("--cloud-url <url>","Override Shogo Cloud URL for this run").action((e,t)=>k(()=>Wr(e,t)));gt.command("checkout <projectId>").description("Roll the local workspace to a specific git checkpoint (SHA or named checkpoint)").option("--at <ref>","Target SHA or checkpoint name (default: remote HEAD)").option("--unshallow","Fetch full history before checking out (needed for old SHAs)").option("--into <dir>","Local dir override (default: ~/.shogo/projects/<projectId>)").option("--api-key <key>","Override API key for this run").option("--cloud-url <url>","Override Shogo Cloud URL for this run").action((e,t)=>k(()=>Ur(e,t)));L.command("doctor").description("Diagnose & repair a wedged local Shogo database (clears failed migrations so the app can reboot)").option("--check","Detect only — never modify the database").option("--yes","Repair without the confirmation prompt").option("--db <path>","Path to shogo.db (default: the desktop app's local database)").option("--bun <path>","Path to a bun binary (default: bundled/PATH bun)").option("--no-backup","Skip the pre-repair database backup (discouraged)").action((e)=>k(()=>Kr(e)));L.command("chat",{isDefault:!0}).description("Start the interactive coding agent in the current directory").argument("[prompt...]","Optional one-shot prompt (runs non-interactively, like -p)").option("-p, --print <prompt>","Headless one-shot: run a single turn and print the result").option("--model <model>","Model id to use for new turns").option("--cwd <dir>","Working directory to operate in (default: $PWD)").option("--runtime-bin <path>","Override the agent-runtime binary path").option("--no-tui","Disable the rich TUI; use a plain readline renderer").option("--api-key <key>","Override API key for this run").option("--cloud-url <url>","Override Shogo Cloud URL for this run").action((e,t)=>k(()=>Vr({print:t.print??(e.length?e.join(" "):void 0),model:t.model,cwd:t.cwd,runtimeBin:t.runtimeBin,noTui:t.tui===!1,apiKey:t.apiKey,cloudUrl:t.cloudUrl})));L.showHelpAfterError(qr.dim(`
|
|
68
|
+
(use --help for usage)`));L.parseAsync(process.argv);async function k(e){try{await e()}catch(t){if(console.error(qr.red(`✗ ${t?.message??t}`)),process.env.SHOGO_DEBUG)console.error(t?.stack);process.exit(1)}}
|