@shogo-ai/worker 2.3.4 → 2.3.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,4 +1,4 @@
1
- import{createRequire as Ar}from"node:module";var ot=(e,t,r)=>()=>{if(e)try{t=e(e=0)}catch(o){r=[o]}if(r)throw r[0];return t};var _e=Ar(import.meta.url);import*as R from"node:child_process";import{promisify as lt}from"node:util";function ct(e){return!!e&&typeof e==="object"&&!Array.isArray(e)}function ut(e){if(e.windowsHide!==void 0)return e;return{...e,windowsHide:!0}}function pt(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(ct(r))return t[1]=ut(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(ct(t[i]))return t[i]=ut(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 K(e,t){let r=function(...o){let n=t();return Reflect.apply(n,this,e(o))};return Object.defineProperty(r,at,{enumerable:!1,configurable:!0,writable:!1,value(...o){let n=t(),i=n[at];if(typeof i==="function")return Reflect.apply(i,n,e(o));let s=lt(n);return Reflect.apply(s,this,e(o))}}),r}var at,T,X,li,ci,dt,Ae,ui;var W=ot(()=>{at=lt.custom;T=K(ie,()=>R.spawn),X=K(ie,()=>R.spawnSync),li=K(pt,()=>R.exec),ci=K(pt,()=>R.execSync),dt=K(ie,()=>R.execFile),Ae=K(ie,()=>R.execFileSync),ui=K(ie,()=>R.fork)});import{existsSync as Ue}from"node:fs";import{join as Ne}from"node:path";import{promisify as ao}from"node:util";async function Ct(e=!1){if(se!==null&&!e)return se;try{await lo("git",["--version"],{timeout:3000}),se=!0}catch{se=!1}return se}function ye(e,t){return`${e.replace(/\/+$/,"")}/api/projects/${t}/git`}function E(e,t={}){let{cwd:r,env:o,timeoutMs:n=300000,logger:i}=t;return new Promise((s,a)=>{let c=T("git",e,{cwd:r,env:{...process.env,...o},stdio:["ignore","pipe","pipe"]}),l="",u="",p=[],m=[];c.stdout.setEncoding("utf-8"),c.stderr.setEncoding("utf-8"),c.stdout.on("data",(d)=>{if(p.push(d),i)i.log(d.trimEnd())}),c.stderr.on("data",(d)=>{if(m.push(d),i)i.warn(d.trimEnd())});let f=setTimeout(()=>{try{c.kill("SIGKILL")}catch{}a(Error(`git ${e[0]} timed out after ${n}ms`))},n);c.on("error",(d)=>{clearTimeout(f),a(d)}),c.on("close",(d)=>{clearTimeout(f),l=p.join(""),u=m.join("");let w=d??-1;if(w===0)s({stdout:l,stderr:u,exitCode:w});else{let g=Error(`git ${e[0]} exited with code ${w}: ${u.slice(0,500)}`);g.exitCode=w,g.stdout=l,g.stderr=u,a(g)}})})}async function Ot(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,shallow:i=!0,logger:s,timeoutMs:a}=e;if(Ue(Ne(n,".git")))throw Error(`cloneProject: ${n}/.git already exists; use gitFetchAndReset instead`);let c=ye(t,o),l=["-c",`http.extraHeader=Authorization: Bearer ${r}`,"clone"];if(i)l.push("--depth=1");return l.push(c,n),await E(l,{logger:s,timeoutMs:a}),{commitSha:(await E(["rev-parse","HEAD"],{cwd:n,logger:s,timeoutMs:1e4})).stdout.trim()}}async function ve(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,branch:i="HEAD",logger:s,timeoutMs:a}=e,c=ye(t,o),l=["-c",`http.extraHeader=Authorization: Bearer ${r}`];return await E([...l,"fetch",c,i],{cwd:n,logger:s,timeoutMs:a}),await E(["reset","--hard","FETCH_HEAD"],{cwd:n,logger:s,timeoutMs:a}),{commitSha:(await E(["rev-parse","HEAD"],{cwd:n,logger:s,timeoutMs:1e4})).stdout.trim()}}async function je(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,logger:i,timeoutMs:s}=e,a=ye(t,o),c=["-c",`http.extraHeader=Authorization: Bearer ${r}`];if(Ue(Ne(n,".git","shallow")))await E([...c,"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",authorEmail:a,authorName:c,logger:l,timeoutMs:u}=e,p=ye(t,o),m={};if(a)m.GIT_AUTHOR_EMAIL=a,m.GIT_COMMITTER_EMAIL=a;if(c)m.GIT_AUTHOR_NAME=c,m.GIT_COMMITTER_NAME=c;await E(["add","-A"],{cwd:n,env:m,logger:l,timeoutMs:u});let f=!1;try{await E(["diff","--cached","--quiet"],{cwd:n,env:m,logger:l,timeoutMs:u})}catch{f=!0}if(!f)return{committed:!1};await E(["commit","-m",i,"--no-verify","--trailer",`Co-authored-by: ${c||process.env.SHOGO_AGENT_GIT_NAME||"Shogo Agent"} <${a||process.env.SHOGO_AGENT_GIT_EMAIL||"agent@shogo.ai"}>`],{cwd:n,env:m,logger:l,timeoutMs:u});let w=(await E(["rev-parse","HEAD"],{cwd:n,env:m,logger:l,timeoutMs:1e4})).stdout.trim(),g=["-c",`http.extraHeader=Authorization: Bearer ${r}`];return await E([...g,"push",p,s],{cwd:n,env:m,logger:l,timeoutMs:u}),{committed:!0,commitSha:w}}function be(e){return Ue(Ne(e,".git"))}var lo,se=null;var Se=ot(()=>{W();lo=ao(dt)});import{Command as Vn}from"commander";import Cr from"picocolors";import h from"picocolors";import{existsSync as Lo}from"node:fs";import{readFileSync as Wr,writeFileSync as Dr,existsSync as Ur,chmodSync as Nr}from"node:fs";import{hostname as jr}from"node:os";import{homedir as Mr}from"node:os";import{join as I}from"node:path";import{mkdirSync as pe}from"node:fs";var j=I(Mr(),".shogo"),F=I(j,"config.json"),ti=I(j,"credentials.json"),ee=I(j,"device-id"),te=I(j,"worker.pid"),Ce=I(j,"logs"),de=I(Ce,"worker.log"),me=I(Ce,"worker.err.log"),re=I(j,"runtime"),A=I(re,process.platform==="win32"?"agent-runtime.exe":"agent-runtime"),oe=I(re,"version.json"),ge=I(j,"projects");function Y(e,t=ge){return I(t,e)}function H(){pe(j,{recursive:!0,mode:448}),pe(Ce,{recursive:!0,mode:448})}function nt(){H(),pe(re,{recursive:!0,mode:448})}function it(e=ge){H(),pe(e,{recursive:!0,mode:448})}var Oe={cloudUrl:"https://studio.shogo.ai",port:8002,projectsDir:ge};function L(){if(!Ur(F))return{};try{return JSON.parse(Wr(F,"utf-8"))}catch(e){throw Error(`Corrupt config at ${F}: ${e.message}`)}}function ne(e){H(),Dr(F,JSON.stringify(e,null,2),{mode:384}),Nr(F,384)}function st(e,t){return{...e,...Object.fromEntries(Object.entries(t).filter(([,r])=>r!==void 0))}}function M(e={}){let t=L(),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=st(st(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??Oe.cloudUrl,name:o.name??jr(),workerDir:o.workerDir??process.cwd(),port:o.port??Oe.port,projectsDir:o.projectsDir??Oe.projectsDir}}W();import{existsSync as Fr,openSync as mt,readFileSync as Hr,unlinkSync as Lr,writeFileSync as Gr}from"node:fs";function he(){if(!Fr(te))return null;let e=Hr(te,"utf-8").trim(),t=parseInt(e,10);return Number.isFinite(t)?t:null}function fe(e){try{return process.kill(e,0),!0}catch{return!1}}function Me(){try{Lr(te)}catch{}}function gt(e){H();let t=he();if(t&&fe(t))throw Error(`Worker already running (pid=${t}). Run \`shogo worker stop\` first.`);if(t)Me();let r=e.inheritStdio?["ignore","inherit","inherit"]:["ignore",mt(de,"a"),mt(me,"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(Gr(te,String(o.pid),{mode:384}),e.detach)o.unref();return{pid:o.pid,child:o}}function ht(e="SIGTERM"){let t=he();if(!t)return{killedPid:null};if(!fe(t))return Me(),{killedPid:null};try{process.kill(t,e)}catch{}return Me(),{killedPid:t}}import _ from"picocolors";import{existsSync as Kr}from"node:fs";import{request as Br}from"node:http";function wt(e,t=process.env){let r=(i)=>i&&i.trim().length>0?i.trim():void 0,o=r(e);if(o)return{url:ft(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:ft(a),source:i}}return null}function ft(e){if(/^https?:\/\//i.test(e))return e;return`http://${e}`}function We(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 yt(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,c=s.port?parseInt(s.port,10):s.protocol==="https:"?443:80,l=Br({method:"CONNECT",host:a,port:c,path:`${t}:443`,timeout:r});l.on("connect",(u)=>{l.destroy();let p=u.statusCode??0;if(p===200)i({ok:!0,detail:`CONNECT ${t}:443 → 200 (via ${a}:${c})`});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}`})}),l.on("timeout",()=>{l.destroy(),i({ok:!1,detail:`timeout after ${r}ms`})}),l.on("error",(u)=>{let p=u.code?` (${u.code})`:"";i({ok:!1,detail:`${u.message}${p}`})}),l.end()}catch(s){i({ok:!1,detail:s?.message??"unknown error"})}})}function vt(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 qr=5000;async function Jr(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 bt=(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 Kr(e.workerDir)?{ok:!0,detail:e.workerDir}:{ok:!1,detail:`${e.workerDir} does not exist`}}}];if(e.proxy)t.push({name:`Proxy reachable (${Vr(e.proxy.url)})`,criticality:"fatal",async run(){return yt(e.proxy)}});let r=vt(e.cloudUrl);for(let o of r)t.push({name:`Reach ${o.host}${o.purpose!=="control"?_.dim(` (${o.purpose})`):""}`,criticality:o.criticality,run:()=>Jr(o.url,qr)});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 Vr(e){try{return new URL(e).host}catch{return e}}async function St(e){console.log(_.bold(`
1
+ import{createRequire as Ar}from"node:module";var ot=(e,t,r)=>()=>{if(e)try{t=e(e=0)}catch(o){r=[o]}if(r)throw r[0];return t};var _e=Ar(import.meta.url);import*as R from"node:child_process";import{promisify as lt}from"node:util";function ct(e){return!!e&&typeof e==="object"&&!Array.isArray(e)}function ut(e){if(e.windowsHide!==void 0)return e;return{...e,windowsHide:!0}}function pt(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(ct(r))return t[1]=ut(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(ct(t[i]))return t[i]=ut(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 K(e,t){let r=function(...o){let n=t();return Reflect.apply(n,this,e(o))};return Object.defineProperty(r,at,{enumerable:!1,configurable:!0,writable:!1,value(...o){let n=t(),i=n[at];if(typeof i==="function")return Reflect.apply(i,n,e(o));let s=lt(n);return Reflect.apply(s,this,e(o))}}),r}var at,T,X,li,ci,dt,Ae,ui;var W=ot(()=>{at=lt.custom;T=K(ie,()=>R.spawn),X=K(ie,()=>R.spawnSync),li=K(pt,()=>R.exec),ci=K(pt,()=>R.execSync),dt=K(ie,()=>R.execFile),Ae=K(ie,()=>R.execFileSync),ui=K(ie,()=>R.fork)});import{existsSync as Ue}from"node:fs";import{join as Ne}from"node:path";import{promisify as ao}from"node:util";async function Ct(e=!1){if(se!==null&&!e)return se;try{await lo("git",["--version"],{timeout:3000}),se=!0}catch{se=!1}return se}function ye(e,t){return`${e.replace(/\/+$/,"")}/api/projects/${t}/git`}function E(e,t={}){let{cwd:r,env:o,timeoutMs:n=300000,logger:i}=t;return new Promise((s,a)=>{let c=T("git",e,{cwd:r,env:{...process.env,...o},stdio:["ignore","pipe","pipe"]}),l="",u="",p=[],m=[];c.stdout.setEncoding("utf-8"),c.stderr.setEncoding("utf-8"),c.stdout.on("data",(d)=>{if(p.push(d),i)i.log(d.trimEnd())}),c.stderr.on("data",(d)=>{if(m.push(d),i)i.warn(d.trimEnd())});let f=setTimeout(()=>{try{c.kill("SIGKILL")}catch{}a(Error(`git ${e[0]} timed out after ${n}ms`))},n);c.on("error",(d)=>{clearTimeout(f),a(d)}),c.on("close",(d)=>{clearTimeout(f),l=p.join(""),u=m.join("");let w=d??-1;if(w===0)s({stdout:l,stderr:u,exitCode:w});else{let h=Error(`git ${e[0]} exited with code ${w}: ${u.slice(0,500)}`);h.exitCode=w,h.stdout=l,h.stderr=u,a(h)}})})}async function Ot(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,shallow:i=!0,logger:s,timeoutMs:a}=e;if(Ue(Ne(n,".git")))throw Error(`cloneProject: ${n}/.git already exists; use gitFetchAndReset instead`);let c=ye(t,o),l=["-c",`http.extraHeader=Authorization: Bearer ${r}`,"clone"];if(i)l.push("--depth=1");return l.push(c,n),await E(l,{logger:s,timeoutMs:a}),{commitSha:(await E(["rev-parse","HEAD"],{cwd:n,logger:s,timeoutMs:1e4})).stdout.trim()}}async function ve(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,branch:i="HEAD",logger:s,timeoutMs:a}=e,c=ye(t,o),l=["-c",`http.extraHeader=Authorization: Bearer ${r}`];return await E([...l,"fetch",c,i],{cwd:n,logger:s,timeoutMs:a}),await E(["reset","--hard","FETCH_HEAD"],{cwd:n,logger:s,timeoutMs:a}),{commitSha:(await E(["rev-parse","HEAD"],{cwd:n,logger:s,timeoutMs:1e4})).stdout.trim()}}async function je(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,logger:i,timeoutMs:s}=e,a=ye(t,o),c=["-c",`http.extraHeader=Authorization: Bearer ${r}`];if(Ue(Ne(n,".git","shallow")))await E([...c,"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",authorEmail:a,authorName:c,logger:l,timeoutMs:u}=e,p=ye(t,o),m={};if(a)m.GIT_AUTHOR_EMAIL=a,m.GIT_COMMITTER_EMAIL=a;if(c)m.GIT_AUTHOR_NAME=c,m.GIT_COMMITTER_NAME=c;await E(["add","-A"],{cwd:n,env:m,logger:l,timeoutMs:u});let f=!1;try{await E(["diff","--cached","--quiet"],{cwd:n,env:m,logger:l,timeoutMs:u})}catch{f=!0}if(!f)return{committed:!1};await E(["commit","-m",i,"--no-verify","--trailer",`Co-authored-by: ${c||process.env.SHOGO_AGENT_GIT_NAME||"Shogo Agent"} <${a||process.env.SHOGO_AGENT_GIT_EMAIL||"agent@shogo.ai"}>`],{cwd:n,env:m,logger:l,timeoutMs:u});let w=(await E(["rev-parse","HEAD"],{cwd:n,env:m,logger:l,timeoutMs:1e4})).stdout.trim(),h=["-c",`http.extraHeader=Authorization: Bearer ${r}`];return await E([...h,"push",p,s],{cwd:n,env:m,logger:l,timeoutMs:u}),{committed:!0,commitSha:w}}function Se(e){return Ue(Ne(e,".git"))}var lo,se=null;var be=ot(()=>{W();lo=ao(dt)});import{Command as Vn}from"commander";import Cr from"picocolors";import g from"picocolors";import{existsSync as Lo}from"node:fs";import{readFileSync as Wr,writeFileSync as Dr,existsSync as Ur,chmodSync as Nr}from"node:fs";import{hostname as jr}from"node:os";import{homedir as Mr}from"node:os";import{join as I}from"node:path";import{mkdirSync as pe}from"node:fs";var j=I(Mr(),".shogo"),F=I(j,"config.json"),ti=I(j,"credentials.json"),ee=I(j,"device-id"),te=I(j,"worker.pid"),Ce=I(j,"logs"),de=I(Ce,"worker.log"),me=I(Ce,"worker.err.log"),re=I(j,"runtime"),A=I(re,process.platform==="win32"?"agent-runtime.exe":"agent-runtime"),oe=I(re,"version.json"),he=I(j,"projects");function Y(e,t=he){return I(t,e)}function H(){pe(j,{recursive:!0,mode:448}),pe(Ce,{recursive:!0,mode:448})}function nt(){H(),pe(re,{recursive:!0,mode:448})}function it(e=he){H(),pe(e,{recursive:!0,mode:448})}var Oe={cloudUrl:"https://studio.shogo.ai",port:8002,projectsDir:he};function L(){if(!Ur(F))return{};try{return JSON.parse(Wr(F,"utf-8"))}catch(e){throw Error(`Corrupt config at ${F}: ${e.message}`)}}function ne(e){H(),Dr(F,JSON.stringify(e,null,2),{mode:384}),Nr(F,384)}function st(e,t){return{...e,...Object.fromEntries(Object.entries(t).filter(([,r])=>r!==void 0))}}function M(e={}){let t=L(),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=st(st(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??Oe.cloudUrl,name:o.name??jr(),workerDir:o.workerDir??process.cwd(),port:o.port??Oe.port,projectsDir:o.projectsDir??Oe.projectsDir}}W();import{existsSync as Fr,openSync as mt,readFileSync as Hr,unlinkSync as Lr,writeFileSync as Gr}from"node:fs";function ge(){if(!Fr(te))return null;let e=Hr(te,"utf-8").trim(),t=parseInt(e,10);return Number.isFinite(t)?t:null}function fe(e){try{return process.kill(e,0),!0}catch{return!1}}function Me(){try{Lr(te)}catch{}}function ht(e){H();let t=ge();if(t&&fe(t))throw Error(`Worker already running (pid=${t}). Run \`shogo worker stop\` first.`);if(t)Me();let r=e.inheritStdio?["ignore","inherit","inherit"]:["ignore",mt(de,"a"),mt(me,"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(Gr(te,String(o.pid),{mode:384}),e.detach)o.unref();return{pid:o.pid,child:o}}function gt(e="SIGTERM"){let t=ge();if(!t)return{killedPid:null};if(!fe(t))return Me(),{killedPid:null};try{process.kill(t,e)}catch{}return Me(),{killedPid:t}}import _ from"picocolors";import{existsSync as Kr}from"node:fs";import{request as Br}from"node:http";function wt(e,t=process.env){let r=(i)=>i&&i.trim().length>0?i.trim():void 0,o=r(e);if(o)return{url:ft(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:ft(a),source:i}}return null}function ft(e){if(/^https?:\/\//i.test(e))return e;return`http://${e}`}function We(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 yt(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,c=s.port?parseInt(s.port,10):s.protocol==="https:"?443:80,l=Br({method:"CONNECT",host:a,port:c,path:`${t}:443`,timeout:r});l.on("connect",(u)=>{l.destroy();let p=u.statusCode??0;if(p===200)i({ok:!0,detail:`CONNECT ${t}:443 → 200 (via ${a}:${c})`});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}`})}),l.on("timeout",()=>{l.destroy(),i({ok:!1,detail:`timeout after ${r}ms`})}),l.on("error",(u)=>{let p=u.code?` (${u.code})`:"";i({ok:!1,detail:`${u.message}${p}`})}),l.end()}catch(s){i({ok:!1,detail:s?.message??"unknown error"})}})}function vt(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 qr=5000;async function Jr(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 St=(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 Kr(e.workerDir)?{ok:!0,detail:e.workerDir}:{ok:!1,detail:`${e.workerDir} does not exist`}}}];if(e.proxy)t.push({name:`Proxy reachable (${Vr(e.proxy.url)})`,criticality:"fatal",async run(){return yt(e.proxy)}});let r=vt(e.cloudUrl);for(let o of r)t.push({name:`Reach ${o.host}${o.purpose!=="control"?_.dim(` (${o.purpose})`):""}`,criticality:o.criticality,run:()=>Jr(o.url,qr)});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 Vr(e){try{return new URL(e).host}catch{return e}}async function bt(e){console.log(_.bold(`
2
2
  Shogo Worker — Preflight
3
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.
@@ -7,24 +7,24 @@ Starting with ${r} optional host(s) blocked. See docs/my-machines-networking.md.
7
7
  `));else console.log(_.red(`
8
8
  Preflight failed — fix blocking issues before starting.
9
9
  `));return!t}import{accessSync as Yr,constants as Xr,existsSync as zr}from"node:fs";import{delimiter as Qr,join as Zr}from"node:path";function G(e={}){let t=e.env??process.env,r=eo(e);for(let i of r)if(Pt(i.path))return i;let o=e.systemBinName??kt(),n=to(o,t.PATH);if(n)return{path:n,source:"path"};return null}function eo(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:A,source:"home"}),r}function kt(){return process.platform==="win32"?"shogo-agent-runtime.exe":"shogo-agent-runtime"}function Pt(e){if(!zr(e))return!1;try{if(process.platform==="win32")return!0;return Yr(e,Xr.X_OK),!0}catch{return!1}}function to(e,t){if(!t)return null;for(let r of t.split(Qr)){if(!r)continue;let o=Zr(r,e);if(Pt(o))return o}return null}function q(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(` ${A} (default install location)`),r.push(` ${kt()} 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 Kt,randomBytes as yo}from"node:crypto";import{existsSync as Fe,mkdirSync as Mt,readdirSync as qt,rmSync as vo}from"node:fs";import{createConnection as bo}from"node:net";import{tmpdir as Wt}from"node:os";import{dirname as So,join as J}from"node:path";W();import{existsSync as ro,mkdirSync as oo,readFileSync as no,writeFileSync as we}from"node:fs";import{join as z}from"node:path";var Rt=1e5;function Tt(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,cpuPercent:o,tasksMax:i}}function $t(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 io(){if(process.platform!=="linux")return!1;try{return X("systemd-run",["--version"],{stdio:"ignore",timeout:3000}).status===0}catch{return!1}}function Et(e){let{command:t,args:r,limits:o,scopeName:n,logger:i}=e;if(process.platform!=="linux"||!io())return{command:t,args:r,wrapped:!1};let s=["-p",`MemoryMax=${o.memoryMB}M`,"-p",`MemoryHigh=${Math.max(128,Math.floor(o.memoryMB*0.9))}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.memoryMB}M)`),{command:"systemd-run",args:a,wrapped:!0}}function xt(e){let{pid:t,limits:r,logger:o}=e;if(process.platform!=="linux")return!1;let n="/sys/fs/cgroup";if(!ro(z(n,"cgroup.controllers")))return!1;let i=z(n,"shogo",`rt-${t}`);try{if(oo(i,{recursive:!0}),we(z(i,"memory.max"),String(r.memoryMB*1024*1024)),r.cpuPercent>0){let s=Math.floor(r.cpuPercent/100*Rt);we(z(i,"cpu.max"),`${s} ${Rt}`)}try{we(z(i,"pids.max"),String(r.tasksMax))}catch{}return we(z(i,"cgroup.procs"),String(t)),o?.log(`[resource-limits] attached pid ${t} to cgroup ${i} (memory.max=${r.memoryMB}M)`),!0}catch(s){return o?.warn(`[resource-limits] direct cgroup attach failed for pid ${t}: ${s?.message??s}`),!1}}function It(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=_e(n);if(typeof i.limitProcess!=="function")return!1;let s=i.limitProcess(t,{memoryBytes:r.memoryMB*1024*1024,killOnClose:!0});if(s)o?.log(`[resource-limits] attached pid ${t} to Job Object (${r.memoryMB}M)`);return s}catch{return!1}}function _t(e){try{let t=JSON.parse(no(e,"utf-8"));if(!Array.isArray(t.pids))return[];return t.pids.filter((r)=>Number.isInteger(r)&&r>0)}catch{return[]}}function so(e,t=process.platform,r=[]){try{if(t==="win32"){let s=r.filter((u)=>Number.isInteger(u)&&u>0&&u!==e),a=`$ErrorActionPreference='SilentlyContinue';$root=${e};$exclude=@(${s.join(",")});$all=Get-CimInstance Win32_Process;$seen=@{};$stack=New-Object System.Collections.Stack;$stack.Push($root);$sum=0;while($stack.Count -gt 0){$id=$stack.Pop();if($seen[$id]){continue};$seen[$id]=$true;foreach($p in $all){if($p.ProcessId -eq $id){$sum+=$p.WorkingSetSize};if($p.ParentProcessId -eq $id -and $exclude -notcontains $p.ProcessId){$stack.Push($p.ProcessId)}}};[math]::Floor($sum/1024)`,c=X("powershell.exe",["-NoProfile","-NonInteractive","-Command",a],{encoding:"utf-8",timeout:1e4,windowsHide:!0});if(c.status!==0||!c.stdout)return null;let l=parseInt(c.stdout.trim(),10);return Number.isFinite(l)?l:null}let o=X("ps",["-o","rss=","-g",String(e)],{encoding:"utf-8",timeout:5000});if(o.status!==0||!o.stdout)return null;let n=0,i=!1;for(let s of o.stdout.split(`
11
- `)){let a=parseInt(s.trim(),10);if(Number.isFinite(a))n+=a,i=!0}return i?n:null}catch{return null}}class De{timer=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.timer)return;this.timer=setInterval(()=>this.sample(),Math.max(2000,this.intervalMs));try{this.timer.unref?.()}catch{}}stop(){if(this.timer)clearInterval(this.timer),this.timer=null}sample(){if(this.fired)return;let e=so(this.pid,this.platform,this.excludePids?.()??[]);if(e==null){this.consecutive=0;return}let t=Math.floor(e/1024);if(t<=this.ceilingMB){this.consecutive=0;return}if(this.consecutive+=1,this.logger?.warn(`[resource-limits] pid ${this.pid} RSS ${t}MB over ceiling ${this.ceilingMB}MB (strike ${this.consecutive}/${this.strikes})`),this.consecutive>=this.strikes)this.fired=!0,this.stop(),this.onBreach(t)}}import{CloudFileTransport as He}from"@shogo-ai/sdk/cloud-file-transport";import{killViteWatchFromPidfile as Dt}from"@shogo-ai/sdk/vite-watch";import{watch as At,statSync as uo}from"node:fs";import{relative as Ni,sep as po,posix as mo}from"node:path";var go=1500,ho=new Set(["node_modules",".git","dist","build",".vite",".cache"]),fo=[".shogo/"];function wo(e){if(!e)return!0;return e.split("/").some((t)=>ho.has(t))}class ae{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??go,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(() => (Se(),{}));return co(t)})}start(){if(this.watcher)return;if(this.stopped)throw Error("CloudSyncWatcher: already stopped, build a fresh instance");try{this.watcher=At(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=At(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(po).join(mo.sep);if(wo(t))return;try{let r=`${this.rootDir}/${t}`;if(uo(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(fo.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,authorEmail:this.git.authorEmail,authorName:this.git.authorName,logger:this.logger});o=s.committed,n=s.commitSha}catch(s){throw s}}this.onFlush?.({uploaded:e,errors:0,committed:o,commitSha:n})}}Se();var Le=37100,Ge=37900,Ut=1,ko=2,ke=16,Po=900000,Ro=30000,To=3000,$o=1000,Eo=60000,Nt=8,jt=300000,Ft=3600000,xo=8,Io=120000,_o=900000,Co=60000,Oo=500,Ao=30000,Ht=25000,Mo=5000,Wo=500,Do=process.env.SHOGO_PERF_LOG==="1";function Pe(e,t,r,o){if(!Do)return;console.log(`[shogo-perf] ${JSON.stringify({perf:"open",source:"worker",phase:e,projectId:t,openAttemptId:r,atMs:Date.now(),...o})}`)}var Uo=(e)=>({command:e,args:[]}),Be=null;function Jt(){if(!Be)Be=yo(32).toString("hex");return Be}function Lt(e){return Kt("sha256",Jt()).update(`runtime:${e}`).digest("hex")}function No(e){return Kt("sha256",Jt()).update(`webhook:${e}`).digest("hex")}function Gt(e){let t=e.indexOf("?");if(t===-1)return{pathname:e,search:""};return{pathname:e.slice(0,t),search:e.slice(t)}}function Bt(e){try{return qt(e).length===0}catch{return!0}}function Ke(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(`
12
- `)}class qe{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;constructor(e={}){this.opts=e,this.log=e.logger??console,this.spawnCommand=e.spawnCommand??Uo,this.sweepOrphanedViteWatches()}resolveBinary(){if(!this.resolved)this.resolved=this.opts.resolveBin?this.opts.resolveBin():G({flag:this.opts.runtimeBin,env:this.opts.env});return this.resolved}async resolveLocalUrl(e,t){let{pathname:r,search:o}=Gt(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 Lt(e)}describeRejection(e,t){let{pathname:r}=Gt(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>=Ro).sort((i,s)=>i.lastUsedAt-s.lastUsedAt);this.evictLru(t,o.length,n,r)}async evictLru(e,t,r,o){let n=t-e;for(let i of r){if(n<=0)break;let s=await this.activeWork(i);if(i.status!=="running"||this.runtimes.get(i.projectId)!==i)continue;if(s){this.log.log(`[WorkerRuntimeManager] maxRuntimes=${e}: not evicting ${i.projectId} (${s.turns} turn(s), ${s.tasks} task(s) active)`);continue}let a=o-i.lastUsedAt;this.log.log(`[WorkerRuntimeManager] maxRuntimes=${e} exceeded (${t} running) — `+`LRU-evicting ${i.projectId} (idle ${Math.round(a/1000)}s)`),this.stop(i.projectId,"SIGTERM","lru-evict").catch((c)=>{this.log.warn(`[WorkerRuntimeManager] maxRuntimes eviction of ${i.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; 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&&Fe(t.projectDir))return t;if(!r)throw Error(Ke(e,"no-auto-pull-config",null));if(!r.projectsDir)throw Error(Ke(e,"no-projects-dir",null));if(!r.enabled){let s=J(r.projectsDir,e);if(Fe(s)&&!Bt(s))return{...t,projectDir:s};throw Error(Ke(e,"auto-pull-disabled",s))}if(this.pulledProjects.has(e))return{...t,projectDir:J(r.projectsDir,e)};let o=J(r.projectsDir,e),n=r.logger??this.log,i={cloneProject:r.gitOps?.cloneProject??Ot,gitIsAvailable:r.gitOps?.gitIsAvailable??Ct,isGitRepo:r.gitOps?.isGitRepo??be};this.pulledProjects.add(e);try{Mt(o,{recursive:!0});let s=Bt(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 He({apiUrl:t.cloudUrl,apiKey:t.apiKey,projectId:e,localDir:o}),m=this.syncModes.get(e)??"files",f=new ae({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 He({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 He({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:t}),s=(await n.listManifest()).filter((c)=>c.path===".shogo"||c.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)}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=Io*Math.pow(2,t);return Math.min(r,_o)}healBreaker(e){e.status="stopped",e.consecutiveFailures=0}async stopAll(e="SIGTERM"){this.stopped=!0;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+Ut;let r=this.buildEnv(e,t.path);if(e.runtimeToken=r.RUNTIME_AUTH_SECRET,r.SHOGO_EXEC_PIDS_FILE)try{vo(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,c=!1;if(i){let d=Et({command:n.command,args:n.args,limits:i,scopeName:`shogo-rt-${e.projectId.slice(0,8)}-${Date.now().toString(36)}`,logger:this.log});s=d.command,a=d.args,c=d.wrapped}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 l=process.platform!=="win32",u=T(s,a,{cwd:o,env:r,detached:l,stdio:["ignore","pipe","pipe"]});if(l)try{u.unref()}catch{}e.proc=u,e.pid=u.pid??null,e.status="starting",e.startedAt=Date.now();let p=e.spawnConfig.extraEnv?.SHOGO_OPEN_ID;if(Pe("agent-spawned",e.projectId,p,{pid:e.pid,command:s,args:a,source:t.source}),i)try{this.applyPostSpawnLimits(e,i,c)}catch(d){this.log.warn(`[WorkerRuntimeManager] failed to apply resource limits for ${e.projectId}: ${d?.message??d}`)}e.lastStdoutAt=e.startedAt,u.on("error",(d)=>{e.lastError=d?.message??String(d),this.log.error(`[WorkerRuntimeManager] spawn error for ${e.projectId}: ${e.lastError}`)}),u.on("exit",(d,w)=>{this.handleExit(e,d,w)});let m=`[runtime:${e.projectId.slice(0,8)}]`,f=!0;u.stdout?.on("data",(d)=>{if(e.lastStdoutAt=Date.now(),f)f=!1,Pe("agent-first-output",e.projectId,p,{elapsedMs:Date.now()-(e.startedAt??Date.now())});for(let w of d.toString().trimEnd().split(`
10
+ `)}W();import{createHmac as Kt,randomBytes as yo}from"node:crypto";import{existsSync as Fe,mkdirSync as Mt,readdirSync as qt,rmSync as vo}from"node:fs";import{createConnection as So}from"node:net";import{tmpdir as Wt}from"node:os";import{dirname as bo,join as J}from"node:path";W();import{existsSync as ro,mkdirSync as oo,readFileSync as no,writeFileSync as we}from"node:fs";import{join as z}from"node:path";var Rt=1e5;function Tt(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,cpuPercent:o,tasksMax:i}}function $t(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 io(){if(process.platform!=="linux")return!1;try{return X("systemd-run",["--version"],{stdio:"ignore",timeout:3000}).status===0}catch{return!1}}function Et(e){let{command:t,args:r,limits:o,scopeName:n,logger:i}=e;if(process.platform!=="linux"||!io())return{command:t,args:r,wrapped:!1};let s=["-p",`MemoryMax=${o.memoryMB}M`,"-p",`MemoryHigh=${Math.max(128,Math.floor(o.memoryMB*0.9))}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.memoryMB}M)`),{command:"systemd-run",args:a,wrapped:!0}}function xt(e){let{pid:t,limits:r,logger:o}=e;if(process.platform!=="linux")return!1;let n="/sys/fs/cgroup";if(!ro(z(n,"cgroup.controllers")))return!1;let i=z(n,"shogo",`rt-${t}`);try{if(oo(i,{recursive:!0}),we(z(i,"memory.max"),String(r.memoryMB*1024*1024)),r.cpuPercent>0){let s=Math.floor(r.cpuPercent/100*Rt);we(z(i,"cpu.max"),`${s} ${Rt}`)}try{we(z(i,"pids.max"),String(r.tasksMax))}catch{}return we(z(i,"cgroup.procs"),String(t)),o?.log(`[resource-limits] attached pid ${t} to cgroup ${i} (memory.max=${r.memoryMB}M)`),!0}catch(s){return o?.warn(`[resource-limits] direct cgroup attach failed for pid ${t}: ${s?.message??s}`),!1}}function It(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=_e(n);if(typeof i.limitProcess!=="function")return!1;let s=i.limitProcess(t,{memoryBytes:r.memoryMB*1024*1024,killOnClose:!0});if(s)o?.log(`[resource-limits] attached pid ${t} to Job Object (${r.memoryMB}M)`);return s}catch{return!1}}function _t(e){try{let t=JSON.parse(no(e,"utf-8"));if(!Array.isArray(t.pids))return[];return t.pids.filter((r)=>Number.isInteger(r)&&r>0)}catch{return[]}}function so(e,t=process.platform,r=[]){try{if(t==="win32"){let s=r.filter((u)=>Number.isInteger(u)&&u>0&&u!==e),a=`$ErrorActionPreference='SilentlyContinue';$root=${e};$exclude=@(${s.join(",")});$all=Get-CimInstance Win32_Process;$seen=@{};$stack=New-Object System.Collections.Stack;$stack.Push($root);$sum=0;while($stack.Count -gt 0){$id=$stack.Pop();if($seen[$id]){continue};$seen[$id]=$true;foreach($p in $all){if($p.ProcessId -eq $id){$sum+=$p.WorkingSetSize};if($p.ParentProcessId -eq $id -and $exclude -notcontains $p.ProcessId){$stack.Push($p.ProcessId)}}};[math]::Floor($sum/1024)`,c=X("powershell.exe",["-NoProfile","-NonInteractive","-Command",a],{encoding:"utf-8",timeout:1e4,windowsHide:!0});if(c.status!==0||!c.stdout)return null;let l=parseInt(c.stdout.trim(),10);return Number.isFinite(l)?l:null}let o=X("ps",["-o","rss=","-g",String(e)],{encoding:"utf-8",timeout:5000});if(o.status!==0||!o.stdout)return null;let n=0,i=!1;for(let s of o.stdout.split(`
11
+ `)){let a=parseInt(s.trim(),10);if(Number.isFinite(a))n+=a,i=!0}return i?n:null}catch{return null}}class De{timer=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.timer)return;this.timer=setInterval(()=>this.sample(),Math.max(2000,this.intervalMs));try{this.timer.unref?.()}catch{}}stop(){if(this.timer)clearInterval(this.timer),this.timer=null}sample(){if(this.fired)return;let e=so(this.pid,this.platform,this.excludePids?.()??[]);if(e==null){this.consecutive=0;return}let t=Math.floor(e/1024);if(t<=this.ceilingMB){this.consecutive=0;return}if(this.consecutive+=1,this.logger?.warn(`[resource-limits] pid ${this.pid} RSS ${t}MB over ceiling ${this.ceilingMB}MB (strike ${this.consecutive}/${this.strikes})`),this.consecutive>=this.strikes)this.fired=!0,this.stop(),this.onBreach(t)}}import{CloudFileTransport as He}from"@shogo-ai/sdk/cloud-file-transport";import{killViteWatchFromPidfile as Dt}from"@shogo-ai/sdk/vite-watch";import{watch as At,statSync as uo}from"node:fs";import{relative as Ni,sep as po,posix as mo}from"node:path";var ho=1500,go=new Set(["node_modules",".git","dist","build",".vite",".cache"]),fo=[".shogo/"];function wo(e){if(!e)return!0;return e.split("/").some((t)=>go.has(t))}class ae{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??ho,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(() => (be(),{}));return co(t)})}start(){if(this.watcher)return;if(this.stopped)throw Error("CloudSyncWatcher: already stopped, build a fresh instance");try{this.watcher=At(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=At(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(po).join(mo.sep);if(wo(t))return;try{let r=`${this.rootDir}/${t}`;if(uo(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(fo.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,authorEmail:this.git.authorEmail,authorName:this.git.authorName,logger:this.logger});o=s.committed,n=s.commitSha}catch(s){throw s}}this.onFlush?.({uploaded:e,errors:0,committed:o,commitSha:n})}}be();var Le=37100,Ge=37900,Ut=1,ko=2,ke=16,Po=900000,Ro=30000,To=3000,$o=1000,Eo=60000,Nt=8,jt=300000,Ft=3600000,xo=8,Io=120000,_o=900000,Co=60000,Oo=500,Ao=30000,Ht=25000,Mo=5000,Wo=500,Do=process.env.SHOGO_PERF_LOG==="1";function Pe(e,t,r,o){if(!Do)return;console.log(`[shogo-perf] ${JSON.stringify({perf:"open",source:"worker",phase:e,projectId:t,openAttemptId:r,atMs:Date.now(),...o})}`)}var Uo=(e)=>({command:e,args:[]}),Be=null;function Jt(){if(!Be)Be=yo(32).toString("hex");return Be}function Lt(e){return Kt("sha256",Jt()).update(`runtime:${e}`).digest("hex")}function No(e){return Kt("sha256",Jt()).update(`webhook:${e}`).digest("hex")}function Gt(e){let t=e.indexOf("?");if(t===-1)return{pathname:e,search:""};return{pathname:e.slice(0,t),search:e.slice(t)}}function Bt(e){try{return qt(e).length===0}catch{return!0}}function Ke(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(`
12
+ `)}class qe{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;constructor(e={}){this.opts=e,this.log=e.logger??console,this.spawnCommand=e.spawnCommand??Uo,this.sweepOrphanedViteWatches()}resolveBinary(){if(!this.resolved)this.resolved=this.opts.resolveBin?this.opts.resolveBin():G({flag:this.opts.runtimeBin,env:this.opts.env});return this.resolved}async resolveLocalUrl(e,t){let{pathname:r,search:o}=Gt(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 Lt(e)}describeRejection(e,t){let{pathname:r}=Gt(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>=Ro).sort((i,s)=>i.lastUsedAt-s.lastUsedAt);this.evictLru(t,o.length,n,r)}async evictLru(e,t,r,o){let n=t-e;for(let i of r){if(n<=0)break;let s=await this.activeWork(i);if(i.status!=="running"||this.runtimes.get(i.projectId)!==i)continue;if(s){this.log.log(`[WorkerRuntimeManager] maxRuntimes=${e}: not evicting ${i.projectId} (${s.turns} turn(s), ${s.tasks} task(s) active)`);continue}let a=o-i.lastUsedAt;this.log.log(`[WorkerRuntimeManager] maxRuntimes=${e} exceeded (${t} running) — `+`LRU-evicting ${i.projectId} (idle ${Math.round(a/1000)}s)`),this.stop(i.projectId,"SIGTERM","lru-evict").catch((c)=>{this.log.warn(`[WorkerRuntimeManager] maxRuntimes eviction of ${i.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; 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&&Fe(t.projectDir))return t;if(!r)throw Error(Ke(e,"no-auto-pull-config",null));if(!r.projectsDir)throw Error(Ke(e,"no-projects-dir",null));if(!r.enabled){let s=J(r.projectsDir,e);if(Fe(s)&&!Bt(s))return{...t,projectDir:s};throw Error(Ke(e,"auto-pull-disabled",s))}if(this.pulledProjects.has(e))return{...t,projectDir:J(r.projectsDir,e)};let o=J(r.projectsDir,e),n=r.logger??this.log,i={cloneProject:r.gitOps?.cloneProject??Ot,gitIsAvailable:r.gitOps?.gitIsAvailable??Ct,isGitRepo:r.gitOps?.isGitRepo??Se};this.pulledProjects.add(e);try{Mt(o,{recursive:!0});let s=Bt(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 He({apiUrl:t.cloudUrl,apiKey:t.apiKey,projectId:e,localDir:o}),m=this.syncModes.get(e)??"files",f=new ae({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 He({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 He({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:t}),s=(await n.listManifest()).filter((c)=>c.path===".shogo"||c.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)}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=Io*Math.pow(2,t);return Math.min(r,_o)}healBreaker(e){e.status="stopped",e.consecutiveFailures=0}async stopAll(e="SIGTERM"){this.stopped=!0;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+Ut;let r=this.buildEnv(e,t.path);if(e.runtimeToken=r.RUNTIME_AUTH_SECRET,r.SHOGO_EXEC_PIDS_FILE)try{vo(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,c=!1;if(i){let d=Et({command:n.command,args:n.args,limits:i,scopeName:`shogo-rt-${e.projectId.slice(0,8)}-${Date.now().toString(36)}`,logger:this.log});s=d.command,a=d.args,c=d.wrapped}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 l=process.platform!=="win32",u=T(s,a,{cwd:o,env:r,detached:l,stdio:["ignore","pipe","pipe"]});if(l)try{u.unref()}catch{}e.proc=u,e.pid=u.pid??null,e.status="starting",e.startedAt=Date.now();let p=e.spawnConfig.extraEnv?.SHOGO_OPEN_ID;if(Pe("agent-spawned",e.projectId,p,{pid:e.pid,command:s,args:a,source:t.source}),i)try{this.applyPostSpawnLimits(e,i,c)}catch(d){this.log.warn(`[WorkerRuntimeManager] failed to apply resource limits for ${e.projectId}: ${d?.message??d}`)}e.lastStdoutAt=e.startedAt,u.on("error",(d)=>{e.lastError=d?.message??String(d),this.log.error(`[WorkerRuntimeManager] spawn error for ${e.projectId}: ${e.lastError}`)}),u.on("exit",(d,w)=>{this.handleExit(e,d,w)});let m=`[runtime:${e.projectId.slice(0,8)}]`,f=!0;u.stdout?.on("data",(d)=>{if(e.lastStdoutAt=Date.now(),f)f=!1,Pe("agent-first-output",e.projectId,p,{elapsedMs:Date.now()-(e.startedAt??Date.now())});for(let w of d.toString().trimEnd().split(`
13
13
  `))if(w)this.log.log(`${m} ${w}`)}),u.stderr?.on("data",(d)=>{if(e.lastStdoutAt=Date.now(),f)f=!1,Pe("agent-first-output",e.projectId,p,{elapsedMs:Date.now()-(e.startedAt??Date.now()),stream:"stderr"});for(let w of d.toString().trimEnd().split(`
14
- `))if(w)this.log.error(`${m} ${w}`)});try{return await this.waitForHealth(e,Ao),Pe("agent-health-ready",e.projectId,p,{elapsedMs:Date.now()-(e.startedAt??Date.now()),agentPort:e.agentPort}),e.status="running",e.lastUsedAt=Date.now(),this.armIdleTimer(e),this.armGraceTimer(e),e}catch(d){e.status="error",e.lastError=d?.message??String(d),this.killProcessGroup(e,"SIGTERM"),this.killViteWatch(e);try{u.kill("SIGTERM")}catch{}throw this.releasePort(e.agentPort),e.agentPort=0,e.apiServerPort=0,d}}resolveLimits(){if(this.resolvedLimits===void 0)this.resolvedLimits=Tt(this.opts.env??process.env);return this.resolvedLimits}applyPostSpawnLimits(e,t,r){let o=e.pid;if(!o)return;if(process.platform==="linux"){if(r)return;if(xt({pid:o,limits:t,logger:this.log}))return}else if(process.platform==="win32"){if(It({pid:o,limits:t,logger:this.log}))return}let n=this.execPidsFile(e);e.limitWatchdog=new De({pid:o,ceilingMB:t.memoryMB,excludePids:()=>_t(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{X("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=[]},Co);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+ko),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:Lt(e.projectId),WEBHOOK_TOKEN:No(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=J(So(t),"tree-sitter-wasm");if(r.extraEnv)Object.assign(o,r.extraEnv);let n=this.resolveLimits();if(n)$t(o,n.memoryMB),o.SHOGO_EXEC_PIDS_FILE=this.execPidsFile(e),o.SHOGO_EXEC_MEMORY_MB??=String(n.memoryMB);return o}execPidsFile(e){return J(Wt(),"shogo-exec-pids",`${e.projectId.replace(/[^\w.-]/g,"_")}.json`)}killViteWatch(e){let t=e.spawnConfig.projectDir;if(!t)return;try{Dt(t,{logger:this.log})}catch(r){this.log.warn(`[WorkerRuntimeManager] Vite watch cleanup for ${e.projectId} failed: ${r?.message??r}`)}}sweepOrphanedViteWatches(){let e=this.opts.autoPull?.projectsDir;if(!e)return;let t;try{t=qt(e,{withFileTypes:!0})}catch{return}for(let r of t){if(!r.isDirectory()||this.runtimes.has(r.name))continue;let o=J(e,r.name);try{let n=Dt(o,{logger:this.log});if(n)this.log.log(`[WorkerRuntimeManager] Reaped orphaned Vite watch for ${r.name} (pid=${n.pid})`)}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&&Fe(t.projectDir))return t.projectDir;let r=this.opts.runtimeWorkDir??J(Wt(),"shogo-runtime",e.projectId);return Mt(r,{recursive:!0}),r}handleExit(e,t,r){let o=r===null&&t===0;if(this.log.log(`[WorkerRuntimeManager] runtime ${e.projectId} exited (code=${t}, signal=${r})`),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),e.pid=null;let n=Date.now(),i=n-e.lastFailureAt<=jt;e.consecutiveFailures=i?e.consecutiveFailures+1:1,e.lastFailureAt=n,e.restarts+=1,e.lastError=`exited code=${t} signal=${r}`,e.restartTimestamps.push(n),e.restartTimestamps=e.restartTimestamps.filter((l)=>n-l<=Ft);let s=e.consecutiveFailures>=Nt,a=e.restartTimestamps.length>=xo;if(s||a){e.status="failed",e.breakerTrips+=1;let l=this.breakerCooldownMs(e.breakerTrips),u=s?`${e.consecutiveFailures} consecutive non-clean exits within ${Math.round(jt/1000)}s`:`${e.restartTimestamps.length} non-clean exits within the last ${Math.round(Ft/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(l/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}/${Nt})`),e.restartTimer)clearTimeout(e.restartTimer);e.restartTimer=setTimeout(()=>{if(e.restartTimer=null,e.status==="failed"||this.stopped)return;e.startPromise=this.doStart(e).then((l)=>(e.startPromise=null,l)).catch((l)=>(e.startPromise=null,this.log.error(`[WorkerRuntimeManager] restart of ${e.projectId} failed: ${l?.message??l}`),e))},c);try{e.restartTimer.unref?.()}catch{}}restartBackoffMs(e){let t=Math.min($o*Math.pow(2,Math.max(0,e-1)),Eo),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??Po;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.activeWork(e);if(this.runtimes.get(e.projectId)!==e||e.status!=="running"||e.idleTimer)return;if(o){this.log.log(`[WorkerRuntimeManager] not idle-evicting ${e.projectId}: ${o.turns} turn(s), ${o.tasks} task(s), ${o.streams} stream(s) active`),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);return!!t&&!!await this.activeWork(t)}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(To)});if(!t.ok)return null;let r=await t.json(),o={turns:r.activeTurns??0,tasks:r.activeTasks??0,streams:r.activeStreams??0};return o.turns+o.tasks+o.streams>0?o:null}catch{return null}}async allocatePort(){let e=Ge-Le,t=Math.min(e,50);for(let r=0;r<t;r++){let o=Le+Math.floor(Math.random()*e);if(o+ke-1>Ge)continue;let n=!0;for(let a=0;a<ke;a++)if(this.usedPorts.has(o+a)){n=!1;break}if(!n)continue;let i=await this.isPortListening(o),s=await this.isPortListening(o+Ut);if(i||s)continue;for(let a=0;a<ke;a++)this.usedPorts.add(o+a);return o}throw Error(`Cannot allocate port in range ${Le}-${Ge} after ${t} attempts`)}releasePort(e){if(!e)return;for(let t=0;t<ke;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=bo({host:"127.0.0.1",port:e});n.setTimeout(Wo),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,c=0,l=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}`);l++;let f=new AbortController,d=setTimeout(()=>f.abort(),1500);try{let g=await fetch(`http://127.0.0.1:${r}/health`,{method:"GET",signal:f.signal});if(clearTimeout(d),g.ok){this.log.log(`[WorkerRuntimeManager] /health ready for ${e.projectId} on port ${r} (HTTP ${g.status} after ${Date.now()-n}ms, ${p} iter, ${l} http)`);return}s=`HTTP /health returned ${g.status}`}catch(g){clearTimeout(d);let S=g?.name??"Error",Z=g?.code??g?.cause?.code;s=`HTTP /health failed: ${S}${Z?`(${Z})`:""}: ${g?.message??g}`}if(u++,a=await this.tcpProbe(r),a){c=Date.now();let g=Date.now()-e.lastStdoutAt;if(g>=Ht)throw Error(`agent-runtime wedged on port ${r}: TCP listening but stdout silent for ${g}ms (> ${Ht}ms window); ${l} /health attempts, last error: ${s??"n/a"}`)}let w=Date.now();if(w-m>=Mo){let g=w-n,S=w-e.lastStdoutAt,Z=c>0?w-c:null;this.log.log(`[WorkerRuntimeManager] still waiting for /health on ${e.projectId} port ${r} (${(g/1000).toFixed(1)}s elapsed, tcpListening=${a}${Z!=null?`(${Z}ms ago)`:""}, lastStdout=${S}ms ago, ${l} http, ${u} tcp, lastError=${s??"n/a"})`),m=w}await new Promise((g)=>setTimeout(g,Oo))}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=${l}, 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 Je,platform as Ve,arch as Ye}from"node:os";function V(){if(typeof __SHOGO_WORKER_VERSION__==="string"&&__SHOGO_WORKER_VERSION__.length>0)return`shogo-cli/${__SHOGO_WORKER_VERSION__}`;return"shogo-cli/unknown"}class zt 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 le=60,Re=300,Xe=3,jo=Xe,Fo=1800000,Ho=25000,Vt=1000,Yt=60000,Xt=3;class ze{opts;log;pollTimer=null;ws=null;wsIdleTimer=null;heartbeatTimer=null;stopped=!1;currentPollInterval=le;wsReconnectAttempt=0;lastHeartbeatError=null;consecutiveAuthFailures=0;consecutiveAuthSuccesses=0;serverPublishedWsUrl=null;activeAbortControllers=new Map;constructor(e){this.opts=e,this.log=e.logger??console}start(){if(!this.opts.apiKey){this.log.log("[WorkerTunnel] No API key set, skipping tunnel");return}this.stopped=!1,this.wsReconnectAttempt=0,this.currentPollInterval=le,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(/\/$/,"")}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 zt;return new WebSocket(e,t)}getReconnectDelayMs(){let e=Math.min(Vt*Math.pow(2,this.wsReconnectAttempt),Yt),t=e*0.2*Math.random();return e+t}async collectMetadata(){let e={hostname:Je(),os:Ve(),arch:Ye(),uptime:process.uptime(),protocolVersion:Xt,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}return e}async sendHeartbeat(){let e=this.getCloudUrl(),t=await this.collectMetadata(),r=Je(),o=this.opts.name??r,n=await fetch(`${e}/api/instances/heartbeat`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":V(),"x-api-key":this.opts.apiKey,"x-shogo-kind":this.opts.kind??"cli-worker"},body:JSON.stringify({hostname:r,name:o,os:Ve(),arch:Ye(),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.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||le;if(this.consecutiveAuthFailures>=Xe){if(this.consecutiveAuthSuccesses++,this.consecutiveAuthSuccesses<jo){this.currentPollInterval=Re,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>=Xe){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!==Re)this.log.warn(`[WorkerTunnel] ${this.consecutiveAuthFailures} consecutive auth failures — `+`backing off to ${Re}s. Re-authenticate with \`shogo login\`.`);this.currentPollInterval=Re}else this.currentPollInterval=le}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?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:c,value:l}=await s.read();if(c)break;if(this.ws?.readyState!==WebSocket.OPEN)break;this.sendFrame({type:"stream-chunk",requestId:e.requestId,data:a.decode(l,{stream:!0})})}if(this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-end",requestId:e.requestId})}catch(c){if(c?.name!=="AbortError"&&this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-error",requestId:e.requestId,error:c?.message??String(c)})}}else{let s=await i.text(),a={};i.headers.forEach((c,l)=>{a[l]=c}),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{}}},Fo)}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{}},Ho)}connectWs(){if(this.stopped||this.ws)return;let e=this.buildWsUrl(),t=Je(),r=Ve(),o=Ye(),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":V(),"x-shogo-hostname":t,"x-shogo-name":n,"x-shogo-os":r,"x-shogo-arch":o,"x-shogo-kind":this.opts.kind??"cli-worker"}},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 c;try{let l=typeof a.data==="string"?a.data:a.data.toString();c=JSON.parse(l)}catch{return}if(c.type==="ping"){this.sendFrame({type:"pong"}),this.resetWsIdleTimer();return}if(c.type==="cancel"){let l=this.activeAbortControllers.get(c.requestId);if(l)l.abort();return}if(c.type==="request"){this.handleRequest(c).catch((l)=>{this.log.error(`[WorkerTunnel] Error handling request: ${l?.message??l}`)});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 c=this.getReconnectDelayMs();this.log.log(`[WorkerTunnel] Reconnecting in ${Math.round(c/1000)}s (attempt ${this.wsReconnectAttempt})`),this.scheduleNextPoll(Math.ceil(c/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:le,BACKOFF_BASE_MS:Vt,BACKOFF_MAX_MS:Yt,TUNNEL_PROTOCOL_VERSION:Xt,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 Qt(e){let t=M({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=wt(e.proxy);if(!e.foreground)return Go({cfg:t,proxy:r,flags:e});let o=G({flag:e.runtimeBin});if(!o)console.error(h.red(q({flag:e.runtimeBin}))),process.exit(1);if(We(process.env,r),e.debug){if(!await St(bt({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(`
15
- Shogo Worker — Starting`)),console.log(h.dim(" name ")+t.name),console.log(h.dim(" worker-dir ")+t.workerDir),console.log(h.dim(" cloud ")+t.cloudUrl),console.log(h.dim(" runtime ")+`${o.path} ${h.dim(`(via ${o.source})`)}`),console.log(h.dim(" auto-pull ")+(n?h.green("on")+h.dim(` → ${t.projectsDir}`):h.yellow("off"))),console.log(h.dim(" sync mode ")+(i?h.green("git")+h.dim(" (falls back to file transport if git is missing)"):h.yellow("files-only"))),e.project)console.log(h.dim(" project ")+e.project);if(r)console.log(h.dim(" proxy ")+`${r.url} ${h.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"}},c=new qe({runtimeBin:e.runtimeBin,defaultSpawnConfig:a,autoPull:{enabled:n,projectsDir:t.projectsDir,watch:!0,useGit:i}});if(!c.resolveBinary())console.error(h.red(q({flag:e.runtimeBin}))),process.exit(1);let l=new ze({apiKey:t.apiKey,cloudUrl:t.cloudUrl,name:t.name,kind:"cli-worker",resolver:c,onAuthRevoked:(m)=>{console.error(h.red(`✗ Cloud auth revoked: ${m}`)),console.error(h.dim(" Run `shogo login` to re-authenticate; this worker will keep polling at the auth-failure backoff until then."))}}),u=!1,p=async(m)=>{if(u)return;u=!0,console.log(h.dim(`
16
- Received ${m} — shutting down...`));try{l.stop()}catch{}try{await c.stopAll()}catch(f){console.warn(h.yellow(`stopAll: ${f?.message??f}`))}process.exit(0)};process.once("SIGINT",()=>void p("SIGINT")),process.once("SIGTERM",()=>void p("SIGTERM")),process.once("SIGHUP",()=>void p("SIGHUP")),l.start(),console.log(h.green("✓ Worker running. Ctrl-C to stop.")),await new Promise(()=>{})}function Go({cfg:e,proxy:t,flags:r}){let{entry:o,runner:n}=Ko(),i=We({...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=Bo(r),{pid:a}=gt({entry:o,runner:n,env:{...i,SHOGO_DETACHED_ARGS:s.join(" ")},cwd:e.workerDir,detach:!0,inheritStdio:!1});console.log(h.bold(`
17
- 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 Bo(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 Ko(){let e=process.execPath,t=/\bbun(?:-[^/\\]*)?$/.test(e)||typeof globalThis.Bun<"u",r=process.argv[1];if(r&&Lo(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 Zt from"picocolors";async function er(){let{killedPid:e}=ht("SIGTERM");if(e===null){console.log(Zt.dim("No worker running."));return}console.log(Zt.green(`✓ Worker stopped (pid=${e}).`))}import N from"picocolors";async function tr(){let e=he();if(!e){console.log(N.yellow("● stopped")+N.dim(" (no pid file)"));return}if(!fe(e)){console.log(N.red("● dead")+N.dim(` (stale pid ${e})`));return}let t=L();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 qo,statSync as Jo}from"node:fs";import rr from"picocolors";async function or(e){let t=e.err?me:de;if(!qo(t)){console.log(rr.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(Jo(t).size===0)console.log(rr.dim("(log file is empty)"))}}import x from"picocolors";W();import{hostname as Vo,platform as Yo,arch as Xo}from"node:os";import B from"picocolors";class C extends Error{kind;constructor(e,t){super(e);this.kind=t;this.name="CloudLoginError"}}async function nr(e){let t=e.cloudUrl.replace(/\/$/,""),r=e.client??"cli",o=e.deviceName??Vo(),n=e.devicePlatform??`${Yo()}-${Xo()}`,i=e.appVersion??Zo(),s=e.log??((g)=>console.log(g)),a=e.fetchImpl??fetch,c=await a(`${t}/api/cli/login/start`,{method:"POST",headers:{"content-type":"application/json","user-agent":V()},body:JSON.stringify({deviceId:e.deviceId,deviceName:o,devicePlatform:n,deviceAppVersion:i,workspaceId:e.workspaceId,client:r}),signal:AbortSignal.timeout(1e4)}).catch((g)=>{throw new C(`Cannot reach Shogo Cloud at ${t}: ${g?.message??g}`,"transport")});if(!c.ok){let g=await c.text().catch(()=>"");throw new C(`Cloud rejected /api/cli/login/start (HTTP ${c.status}): ${g||"no body"}`,"transport")}let l=await c.json().catch(()=>({}));if(!l?.ok||!l.state||!l.authUrl)throw new C(`Cloud returned a malformed start response: ${l?.error??JSON.stringify(l)}`,"transport");let u=zo(e.pollIntervalMs??l.pollIntervalMs),p=e.timeoutMs??l.expiresInMs,m=Date.now()+p;if(s(""),s(B.bold("Sign in to Shogo Cloud")),s(B.dim(" cloud: ")+t),s(B.dim(" device: ")+`${o} (${n})`),s(B.dim(" user code: ")+B.cyan(l.userCode)),s(""),s(" Open this URL in your browser to approve:"),s(" "+B.cyan(l.authUrl)),s(""),e.openBrowser!==!1)if(typeof e.openBrowser==="function")try{await e.openBrowser(l.authUrl)}catch{}else en(l.authUrl).catch(()=>{});s(B.dim("Waiting for approval..."));let f=e.installSignalHandlers??!0,d=()=>{w.abort()},w=new AbortController;if(e.abortSignal)if(e.abortSignal.aborted)w.abort();else e.abortSignal.addEventListener("abort",()=>w.abort(),{once:!0});if(f)process.once("SIGINT",d),process.once("SIGTERM",d);try{while(!0){if(w.signal.aborted)throw new C("Aborted by caller.","cancelled");if(Date.now()>=m)throw new C(`Timed out after ${Math.round(p/1000)}s waiting for approval.`,"timeout");let g=await a(`${t}/api/cli/login/poll?state=${encodeURIComponent(l.state)}`,{method:"GET",headers:{accept:"application/json","user-agent":V()},signal:AbortSignal.timeout(1e4)}).catch((S)=>(s(B.dim(` (poll error: ${S?.message??S} — retrying)`)),null));if(g&&g.ok){let S=await g.json().catch(()=>({}));if(S?.status==="approved"&&S.key)return{key:S.key,email:S.email??null,workspace:S.workspace??null,deviceId:S.deviceId??e.deviceId,workspaces:S.workspaces??null};if(S?.status==="denied")throw new C("Sign-in was denied in the browser.","denied");if(S?.status==="expired")throw new C("Sign-in request expired before it was approved.","expired")}await Qo(u,w.signal)}}finally{if(f)process.removeListener("SIGINT",d),process.removeListener("SIGTERM",d)}}function zo(e){if(!Number.isFinite(e)||e<=0)return 2000;return Math.min(Math.max(e,1000),1e4)}function Qo(e,t){return new Promise((r,o)=>{if(t?.aborted)return o(new C("Aborted by caller.","cancelled"));let n=setTimeout(()=>{t?.removeEventListener("abort",i),r()},e),i=()=>{clearTimeout(n),o(new C("Aborted by caller.","cancelled"))};t?.addEventListener("abort",i,{once:!0})})}function Zo(){return V()}function en(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()}})}import{readFileSync as tn,writeFileSync as rn,existsSync as on,chmodSync as nn}from"node:fs";import{randomUUID as sn}from"node:crypto";function ir(){if(on(ee)){let t=tn(ee,"utf-8").trim();if(t)return t}H();let e=sn();return rn(ee,e,{mode:384}),nn(ee,384),e}var an="https://studio.shogo.ai";async function sr(e){let t=L(),r=(e.cloudUrl||t.cloudUrl||an).replace(/\/$/,""),o=e.apiKey||process.env.SHOGO_API_KEY;if(o){await ln({key:o,cloudUrl:r,cfg:t,name:e.name});return}let n=ir(),i;try{i=await nr({cloudUrl:r,deviceId:n,deviceName:e.name,workspaceId:e.workspace,openBrowser:!e.noBrowser})}catch(s){if(s instanceof C){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 ln({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 ce from"picocolors";async function ar(){let e=L();if(Object.keys(e).length===0){console.log(ce.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(ce.dim(`file: ${F}`)),console.log(JSON.stringify(t,null,2))}async function lr(e,t){let r=["apiKey","cloudUrl","name","workerDir","port"];if(!r.includes(e))console.error(ce.red(`Unknown key: ${e}`)),console.error(ce.dim(`Allowed: ${r.join(", ")}`)),process.exit(1);let o=L();o[e]=e==="port"?parseInt(t,10):t,ne(o),console.log(ce.green(`✓ ${e} set`))}import b from"picocolors";W();import{createHash as cn}from"node:crypto";import{createWriteStream as un,existsSync as ue,mkdirSync as Qe,readFileSync as pr,renameSync as cr,rmSync as Ze,writeFileSync as pn}from"node:fs";import{tmpdir as dn}from"node:os";import{dirname as mn,join as Te}from"node:path";import{Readable as gn}from"node:stream";import{pipeline as hn}from"node:stream/promises";var fn="https://github.com/shogo-labs/shogo-ai/releases/download";function dr(){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 $e(){if(!ue(oe))return null;try{let e=pr(oe,"utf-8");return JSON.parse(e)}catch{return null}}async function wn(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 ur(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(),c=e==="beta"?"-beta":"-nightly",l=a.find((u)=>u.prerelease&&/^v\d+\.\d+\.\d+-/.test(u.tag_name)&&u.tag_name.includes(c));if(!l)throw Error(`No ${e} runtime release found`);return ur(l.tag_name)}function ur(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 yn(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 hn(gn.fromWeb(r.body),un(t))}async function bn(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 Sn(e){let t=pr(e);return cn("sha256").update(t).digest("hex")}async function kn(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 Pn(e,t){nt();let r=mn(t);if(!ue(r))Qe(r,{recursive:!0,mode:448});let o=`${t}.next`;if(ue(o))Ze(o,{force:!0});cr(e,o);try{if(process.platform!=="win32"){let{chmodSync:n}=_e("node:fs");n(o,493)}}catch{}if(ue(t))Ze(t,{force:!0});cr(o,t)}async function mr(e={}){let t=e.logger??console,r=e.channel??"stable",o=e.baseUrl??process.env.SHOGO_RUNTIME_RELEASES_URL??fn,n=e.target??dr(),i=e.version;if(!i)t.log(`[runtime install] Resolving latest ${r} version...`),i=await wn(r,o),t.log(`[runtime install] Latest ${r} = ${i}`);let s=$e();if(s&&s.version===i&&s.target===n&&!e.force)return t.log(`[runtime install] ${i} (${n}) already installed at ${A} — pass --force to reinstall`),{version:i,target:n,binPath:A,source:s.source,sha256:s.sha256,channel:r};let a=yn(i,n,o);t.log(`[runtime install] Downloading ${a.tarball}`);let c=Te(dn(),`shogo-runtime-install-${process.pid}-${Date.now()}`);Qe(c,{recursive:!0});try{let l=Te(c,a.assetName);await vn(a.tarball,l),t.log("[runtime install] Verifying SHA-256...");let u=await bn(a.sha256),p=Sn(l);if(p!==u)throw Error(`SHA-256 mismatch for ${a.assetName}
14
+ `))if(w)this.log.error(`${m} ${w}`)});try{return await this.waitForHealth(e,Ao),Pe("agent-health-ready",e.projectId,p,{elapsedMs:Date.now()-(e.startedAt??Date.now()),agentPort:e.agentPort}),e.status="running",e.lastUsedAt=Date.now(),this.armIdleTimer(e),this.armGraceTimer(e),e}catch(d){e.status="error",e.lastError=d?.message??String(d),this.killProcessGroup(e,"SIGTERM"),this.killViteWatch(e);try{u.kill("SIGTERM")}catch{}throw this.releasePort(e.agentPort),e.agentPort=0,e.apiServerPort=0,d}}resolveLimits(){if(this.resolvedLimits===void 0)this.resolvedLimits=Tt(this.opts.env??process.env);return this.resolvedLimits}applyPostSpawnLimits(e,t,r){let o=e.pid;if(!o)return;if(process.platform==="linux"){if(r)return;if(xt({pid:o,limits:t,logger:this.log}))return}else if(process.platform==="win32"){if(It({pid:o,limits:t,logger:this.log}))return}let n=this.execPidsFile(e);e.limitWatchdog=new De({pid:o,ceilingMB:t.memoryMB,excludePids:()=>_t(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{X("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=[]},Co);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+ko),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:Lt(e.projectId),WEBHOOK_TOKEN:No(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=J(bo(t),"tree-sitter-wasm");if(r.extraEnv)Object.assign(o,r.extraEnv);let n=this.resolveLimits();if(n)$t(o,n.memoryMB),o.SHOGO_EXEC_PIDS_FILE=this.execPidsFile(e),o.SHOGO_EXEC_MEMORY_MB??=String(n.memoryMB);return o}execPidsFile(e){return J(Wt(),"shogo-exec-pids",`${e.projectId.replace(/[^\w.-]/g,"_")}.json`)}killViteWatch(e){let t=e.spawnConfig.projectDir;if(!t)return;try{Dt(t,{logger:this.log})}catch(r){this.log.warn(`[WorkerRuntimeManager] Vite watch cleanup for ${e.projectId} failed: ${r?.message??r}`)}}sweepOrphanedViteWatches(){let e=this.opts.autoPull?.projectsDir;if(!e)return;let t;try{t=qt(e,{withFileTypes:!0})}catch{return}for(let r of t){if(!r.isDirectory()||this.runtimes.has(r.name))continue;let o=J(e,r.name);try{let n=Dt(o,{logger:this.log});if(n)this.log.log(`[WorkerRuntimeManager] Reaped orphaned Vite watch for ${r.name} (pid=${n.pid})`)}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&&Fe(t.projectDir))return t.projectDir;let r=this.opts.runtimeWorkDir??J(Wt(),"shogo-runtime",e.projectId);return Mt(r,{recursive:!0}),r}handleExit(e,t,r){let o=r===null&&t===0;if(this.log.log(`[WorkerRuntimeManager] runtime ${e.projectId} exited (code=${t}, signal=${r})`),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),e.pid=null;let n=Date.now(),i=n-e.lastFailureAt<=jt;e.consecutiveFailures=i?e.consecutiveFailures+1:1,e.lastFailureAt=n,e.restarts+=1,e.lastError=`exited code=${t} signal=${r}`,e.restartTimestamps.push(n),e.restartTimestamps=e.restartTimestamps.filter((l)=>n-l<=Ft);let s=e.consecutiveFailures>=Nt,a=e.restartTimestamps.length>=xo;if(s||a){e.status="failed",e.breakerTrips+=1;let l=this.breakerCooldownMs(e.breakerTrips),u=s?`${e.consecutiveFailures} consecutive non-clean exits within ${Math.round(jt/1000)}s`:`${e.restartTimestamps.length} non-clean exits within the last ${Math.round(Ft/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(l/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}/${Nt})`),e.restartTimer)clearTimeout(e.restartTimer);e.restartTimer=setTimeout(()=>{if(e.restartTimer=null,e.status==="failed"||this.stopped)return;e.startPromise=this.doStart(e).then((l)=>(e.startPromise=null,l)).catch((l)=>(e.startPromise=null,this.log.error(`[WorkerRuntimeManager] restart of ${e.projectId} failed: ${l?.message??l}`),e))},c);try{e.restartTimer.unref?.()}catch{}}restartBackoffMs(e){let t=Math.min($o*Math.pow(2,Math.max(0,e-1)),Eo),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??Po;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.activeWork(e);if(this.runtimes.get(e.projectId)!==e||e.status!=="running"||e.idleTimer)return;if(o){this.log.log(`[WorkerRuntimeManager] not idle-evicting ${e.projectId}: ${o.turns} turn(s), ${o.tasks} task(s), ${o.streams} stream(s) active`),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);return!!t&&!!await this.activeWork(t)}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(To)});if(!t.ok)return null;let r=await t.json(),o={turns:r.activeTurns??0,tasks:r.activeTasks??0,streams:r.activeStreams??0};return o.turns+o.tasks+o.streams>0?o:null}catch{return null}}async allocatePort(){let e=Ge-Le,t=Math.min(e,50);for(let r=0;r<t;r++){let o=Le+Math.floor(Math.random()*e);if(o+ke-1>Ge)continue;let n=!0;for(let a=0;a<ke;a++)if(this.usedPorts.has(o+a)){n=!1;break}if(!n)continue;let i=await this.isPortListening(o),s=await this.isPortListening(o+Ut);if(i||s)continue;for(let a=0;a<ke;a++)this.usedPorts.add(o+a);return o}throw Error(`Cannot allocate port in range ${Le}-${Ge} after ${t} attempts`)}releasePort(e){if(!e)return;for(let t=0;t<ke;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=So({host:"127.0.0.1",port:e});n.setTimeout(Wo),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,c=0,l=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}`);l++;let f=new AbortController,d=setTimeout(()=>f.abort(),1500);try{let h=await fetch(`http://127.0.0.1:${r}/health`,{method:"GET",signal:f.signal});if(clearTimeout(d),h.ok){this.log.log(`[WorkerRuntimeManager] /health ready for ${e.projectId} on port ${r} (HTTP ${h.status} after ${Date.now()-n}ms, ${p} iter, ${l} http)`);return}s=`HTTP /health returned ${h.status}`}catch(h){clearTimeout(d);let b=h?.name??"Error",Z=h?.code??h?.cause?.code;s=`HTTP /health failed: ${b}${Z?`(${Z})`:""}: ${h?.message??h}`}if(u++,a=await this.tcpProbe(r),a){c=Date.now();let h=Date.now()-e.lastStdoutAt;if(h>=Ht)throw Error(`agent-runtime wedged on port ${r}: TCP listening but stdout silent for ${h}ms (> ${Ht}ms window); ${l} /health attempts, last error: ${s??"n/a"}`)}let w=Date.now();if(w-m>=Mo){let h=w-n,b=w-e.lastStdoutAt,Z=c>0?w-c:null;this.log.log(`[WorkerRuntimeManager] still waiting for /health on ${e.projectId} port ${r} (${(h/1000).toFixed(1)}s elapsed, tcpListening=${a}${Z!=null?`(${Z}ms ago)`:""}, lastStdout=${b}ms ago, ${l} http, ${u} tcp, lastError=${s??"n/a"})`),m=w}await new Promise((h)=>setTimeout(h,Oo))}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=${l}, 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 Je,platform as Ve,arch as Ye}from"node:os";function V(){if(typeof __SHOGO_WORKER_VERSION__==="string"&&__SHOGO_WORKER_VERSION__.length>0)return`shogo-cli/${__SHOGO_WORKER_VERSION__}`;return"shogo-cli/unknown"}class zt 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 le=60,Re=300,Xe=3,jo=Xe,Fo=1800000,Ho=25000,Vt=1000,Yt=60000,Xt=3;class ze{opts;log;pollTimer=null;ws=null;wsIdleTimer=null;heartbeatTimer=null;stopped=!1;currentPollInterval=le;wsReconnectAttempt=0;lastHeartbeatError=null;consecutiveAuthFailures=0;consecutiveAuthSuccesses=0;serverPublishedWsUrl=null;activeAbortControllers=new Map;constructor(e){this.opts=e,this.log=e.logger??console}start(){if(!this.opts.apiKey){this.log.log("[WorkerTunnel] No API key set, skipping tunnel");return}this.stopped=!1,this.wsReconnectAttempt=0,this.currentPollInterval=le,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(/\/$/,"")}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 zt;return new WebSocket(e,t)}getReconnectDelayMs(){let e=Math.min(Vt*Math.pow(2,this.wsReconnectAttempt),Yt),t=e*0.2*Math.random();return e+t}async collectMetadata(){let e={hostname:Je(),os:Ve(),arch:Ye(),uptime:process.uptime(),protocolVersion:Xt,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=Je(),o=this.opts.name??r,n=await fetch(`${e}/api/instances/heartbeat`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":V(),"x-api-key":this.opts.apiKey,"x-shogo-kind":this.opts.kind??"cli-worker"},body:JSON.stringify({hostname:r,name:o,os:Ve(),arch:Ye(),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.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||le;if(this.consecutiveAuthFailures>=Xe){if(this.consecutiveAuthSuccesses++,this.consecutiveAuthSuccesses<jo){this.currentPollInterval=Re,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>=Xe){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!==Re)this.log.warn(`[WorkerTunnel] ${this.consecutiveAuthFailures} consecutive auth failures — `+`backing off to ${Re}s. Re-authenticate with \`shogo login\`.`);this.currentPollInterval=Re}else this.currentPollInterval=le}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?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:c,value:l}=await s.read();if(c)break;if(this.ws?.readyState!==WebSocket.OPEN)break;this.sendFrame({type:"stream-chunk",requestId:e.requestId,data:a.decode(l,{stream:!0})})}if(this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-end",requestId:e.requestId})}catch(c){if(c?.name!=="AbortError"&&this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-error",requestId:e.requestId,error:c?.message??String(c)})}}else{let s=await i.text(),a={};i.headers.forEach((c,l)=>{a[l]=c}),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{}}},Fo)}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{}},Ho)}connectWs(){if(this.stopped||this.ws)return;let e=this.buildWsUrl(),t=Je(),r=Ve(),o=Ye(),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":V(),"x-shogo-hostname":t,"x-shogo-name":n,"x-shogo-os":r,"x-shogo-arch":o,"x-shogo-kind":this.opts.kind??"cli-worker"}},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 c;try{let l=typeof a.data==="string"?a.data:a.data.toString();c=JSON.parse(l)}catch{return}if(c.type==="ping"){this.sendFrame({type:"pong"}),this.resetWsIdleTimer();return}if(c.type==="cancel"){let l=this.activeAbortControllers.get(c.requestId);if(l)l.abort();return}if(c.type==="request"){this.handleRequest(c).catch((l)=>{this.log.error(`[WorkerTunnel] Error handling request: ${l?.message??l}`)});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 c=this.getReconnectDelayMs();this.log.log(`[WorkerTunnel] Reconnecting in ${Math.round(c/1000)}s (attempt ${this.wsReconnectAttempt})`),this.scheduleNextPoll(Math.ceil(c/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:le,BACKOFF_BASE_MS:Vt,BACKOFF_MAX_MS:Yt,TUNNEL_PROTOCOL_VERSION:Xt,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 Qt(e){let t=M({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=wt(e.proxy);if(!e.foreground)return Go({cfg:t,proxy:r,flags:e});let o=G({flag:e.runtimeBin});if(!o)console.error(g.red(q({flag:e.runtimeBin}))),process.exit(1);if(We(process.env,r),e.debug){if(!await bt(St({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(`
15
+ 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"}},c=new qe({runtimeBin:e.runtimeBin,defaultSpawnConfig:a,autoPull:{enabled:n,projectsDir:t.projectsDir,watch:!0,useGit:i}});if(!c.resolveBinary())console.error(g.red(q({flag:e.runtimeBin}))),process.exit(1);let l=new ze({apiKey:t.apiKey,cloudUrl:t.cloudUrl,name:t.name,kind:"cli-worker",resolver:c,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."))}}),u=!1,p=async(m)=>{if(u)return;u=!0,console.log(g.dim(`
16
+ Received ${m} — shutting down...`));try{l.stop()}catch{}try{await c.stopAll()}catch(f){console.warn(g.yellow(`stopAll: ${f?.message??f}`))}process.exit(0)};process.once("SIGINT",()=>void p("SIGINT")),process.once("SIGTERM",()=>void p("SIGTERM")),process.once("SIGHUP",()=>void p("SIGHUP")),l.start(),console.log(g.green("✓ Worker running. Ctrl-C to stop.")),await new Promise(()=>{})}function Go({cfg:e,proxy:t,flags:r}){let{entry:o,runner:n}=Ko(),i=We({...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=Bo(r),{pid:a}=ht({entry:o,runner:n,env:{...i,SHOGO_DETACHED_ARGS:s.join(" ")},cwd:e.workerDir,detach:!0,inheritStdio:!1});console.log(g.bold(`
17
+ 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 Bo(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 Ko(){let e=process.execPath,t=/\bbun(?:-[^/\\]*)?$/.test(e)||typeof globalThis.Bun<"u",r=process.argv[1];if(r&&Lo(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 Zt from"picocolors";async function er(){let{killedPid:e}=gt("SIGTERM");if(e===null){console.log(Zt.dim("No worker running."));return}console.log(Zt.green(`✓ Worker stopped (pid=${e}).`))}import N from"picocolors";async function tr(){let e=ge();if(!e){console.log(N.yellow("● stopped")+N.dim(" (no pid file)"));return}if(!fe(e)){console.log(N.red("● dead")+N.dim(` (stale pid ${e})`));return}let t=L();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 qo,statSync as Jo}from"node:fs";import rr from"picocolors";async function or(e){let t=e.err?me:de;if(!qo(t)){console.log(rr.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(Jo(t).size===0)console.log(rr.dim("(log file is empty)"))}}import x from"picocolors";W();import{hostname as Vo,platform as Yo,arch as Xo}from"node:os";import B from"picocolors";class C extends Error{kind;constructor(e,t){super(e);this.kind=t;this.name="CloudLoginError"}}async function nr(e){let t=e.cloudUrl.replace(/\/$/,""),r=e.client??"cli",o=e.deviceName??Vo(),n=e.devicePlatform??`${Yo()}-${Xo()}`,i=e.appVersion??Zo(),s=e.log??((h)=>console.log(h)),a=e.fetchImpl??fetch,c=await a(`${t}/api/cli/login/start`,{method:"POST",headers:{"content-type":"application/json","user-agent":V()},body:JSON.stringify({deviceId:e.deviceId,deviceName:o,devicePlatform:n,deviceAppVersion:i,workspaceId:e.workspaceId,client:r}),signal:AbortSignal.timeout(1e4)}).catch((h)=>{throw new C(`Cannot reach Shogo Cloud at ${t}: ${h?.message??h}`,"transport")});if(!c.ok){let h=await c.text().catch(()=>"");throw new C(`Cloud rejected /api/cli/login/start (HTTP ${c.status}): ${h||"no body"}`,"transport")}let l=await c.json().catch(()=>({}));if(!l?.ok||!l.state||!l.authUrl)throw new C(`Cloud returned a malformed start response: ${l?.error??JSON.stringify(l)}`,"transport");let u=zo(e.pollIntervalMs??l.pollIntervalMs),p=e.timeoutMs??l.expiresInMs,m=Date.now()+p;if(s(""),s(B.bold("Sign in to Shogo Cloud")),s(B.dim(" cloud: ")+t),s(B.dim(" device: ")+`${o} (${n})`),s(B.dim(" user code: ")+B.cyan(l.userCode)),s(""),s(" Open this URL in your browser to approve:"),s(" "+B.cyan(l.authUrl)),s(""),e.openBrowser!==!1)if(typeof e.openBrowser==="function")try{await e.openBrowser(l.authUrl)}catch{}else en(l.authUrl).catch(()=>{});s(B.dim("Waiting for approval..."));let f=e.installSignalHandlers??!0,d=()=>{w.abort()},w=new AbortController;if(e.abortSignal)if(e.abortSignal.aborted)w.abort();else e.abortSignal.addEventListener("abort",()=>w.abort(),{once:!0});if(f)process.once("SIGINT",d),process.once("SIGTERM",d);try{while(!0){if(w.signal.aborted)throw new C("Aborted by caller.","cancelled");if(Date.now()>=m)throw new C(`Timed out after ${Math.round(p/1000)}s waiting for approval.`,"timeout");let h=await a(`${t}/api/cli/login/poll?state=${encodeURIComponent(l.state)}`,{method:"GET",headers:{accept:"application/json","user-agent":V()},signal:AbortSignal.timeout(1e4)}).catch((b)=>(s(B.dim(` (poll error: ${b?.message??b} — retrying)`)),null));if(h&&h.ok){let b=await h.json().catch(()=>({}));if(b?.status==="approved"&&b.key)return{key:b.key,email:b.email??null,workspace:b.workspace??null,deviceId:b.deviceId??e.deviceId,workspaces:b.workspaces??null};if(b?.status==="denied")throw new C("Sign-in was denied in the browser.","denied");if(b?.status==="expired")throw new C("Sign-in request expired before it was approved.","expired")}await Qo(u,w.signal)}}finally{if(f)process.removeListener("SIGINT",d),process.removeListener("SIGTERM",d)}}function zo(e){if(!Number.isFinite(e)||e<=0)return 2000;return Math.min(Math.max(e,1000),1e4)}function Qo(e,t){return new Promise((r,o)=>{if(t?.aborted)return o(new C("Aborted by caller.","cancelled"));let n=setTimeout(()=>{t?.removeEventListener("abort",i),r()},e),i=()=>{clearTimeout(n),o(new C("Aborted by caller.","cancelled"))};t?.addEventListener("abort",i,{once:!0})})}function Zo(){return V()}function en(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()}})}import{readFileSync as tn,writeFileSync as rn,existsSync as on,chmodSync as nn}from"node:fs";import{randomUUID as sn}from"node:crypto";function ir(){if(on(ee)){let t=tn(ee,"utf-8").trim();if(t)return t}H();let e=sn();return rn(ee,e,{mode:384}),nn(ee,384),e}var an="https://studio.shogo.ai";async function sr(e){let t=L(),r=(e.cloudUrl||t.cloudUrl||an).replace(/\/$/,""),o=e.apiKey||process.env.SHOGO_API_KEY;if(o){await ln({key:o,cloudUrl:r,cfg:t,name:e.name});return}let n=ir(),i;try{i=await nr({cloudUrl:r,deviceId:n,deviceName:e.name,workspaceId:e.workspace,openBrowser:!e.noBrowser})}catch(s){if(s instanceof C){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 ln({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 ce from"picocolors";async function ar(){let e=L();if(Object.keys(e).length===0){console.log(ce.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(ce.dim(`file: ${F}`)),console.log(JSON.stringify(t,null,2))}async function lr(e,t){let r=["apiKey","cloudUrl","name","workerDir","port"];if(!r.includes(e))console.error(ce.red(`Unknown key: ${e}`)),console.error(ce.dim(`Allowed: ${r.join(", ")}`)),process.exit(1);let o=L();o[e]=e==="port"?parseInt(t,10):t,ne(o),console.log(ce.green(`✓ ${e} set`))}import S from"picocolors";W();import{createHash as cn}from"node:crypto";import{createWriteStream as un,existsSync as ue,mkdirSync as Qe,readFileSync as pr,renameSync as cr,rmSync as Ze,writeFileSync as pn}from"node:fs";import{tmpdir as dn}from"node:os";import{dirname as mn,join as Te}from"node:path";import{Readable as hn}from"node:stream";import{pipeline as gn}from"node:stream/promises";var fn="https://github.com/shogo-labs/shogo-ai/releases/download";function dr(){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 $e(){if(!ue(oe))return null;try{let e=pr(oe,"utf-8");return JSON.parse(e)}catch{return null}}async function wn(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 ur(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(),c=e==="beta"?"-beta":"-nightly",l=a.find((u)=>u.prerelease&&/^v\d+\.\d+\.\d+-/.test(u.tag_name)&&u.tag_name.includes(c));if(!l)throw Error(`No ${e} runtime release found`);return ur(l.tag_name)}function ur(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 yn(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 gn(hn.fromWeb(r.body),un(t))}async function Sn(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 bn(e){let t=pr(e);return cn("sha256").update(t).digest("hex")}async function kn(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 Pn(e,t){nt();let r=mn(t);if(!ue(r))Qe(r,{recursive:!0,mode:448});let o=`${t}.next`;if(ue(o))Ze(o,{force:!0});cr(e,o);try{if(process.platform!=="win32"){let{chmodSync:n}=_e("node:fs");n(o,493)}}catch{}if(ue(t))Ze(t,{force:!0});cr(o,t)}async function mr(e={}){let t=e.logger??console,r=e.channel??"stable",o=e.baseUrl??process.env.SHOGO_RUNTIME_RELEASES_URL??fn,n=e.target??dr(),i=e.version;if(!i)t.log(`[runtime install] Resolving latest ${r} version...`),i=await wn(r,o),t.log(`[runtime install] Latest ${r} = ${i}`);let s=$e();if(s&&s.version===i&&s.target===n&&!e.force)return t.log(`[runtime install] ${i} (${n}) already installed at ${A} — pass --force to reinstall`),{version:i,target:n,binPath:A,source:s.source,sha256:s.sha256,channel:r};let a=yn(i,n,o);t.log(`[runtime install] Downloading ${a.tarball}`);let c=Te(dn(),`shogo-runtime-install-${process.pid}-${Date.now()}`);Qe(c,{recursive:!0});try{let l=Te(c,a.assetName);await vn(a.tarball,l),t.log("[runtime install] Verifying SHA-256...");let u=await Sn(a.sha256),p=bn(l);if(p!==u)throw Error(`SHA-256 mismatch for ${a.assetName}
18
18
  expected: ${u}
19
19
  actual: ${p}`);let m=Te(c,"extract");Qe(m,{recursive:!0}),await kn(l,m);let f=Te(m,"agent-runtime");if(!ue(f))throw Error(`Tarball ${a.assetName} did not contain ./agent-runtime`);Pn(f,A);let d={version:i,target:n,installedAt:new Date().toISOString(),channel:r,source:a.tarball,sha256:p};return pn(oe,JSON.stringify(d,null,2)+`
20
- `,{mode:384}),t.log(`[runtime install] Installed agent-runtime ${i} (${n}) to ${A}`),{version:i,target:n,binPath:A,source:a.tarball,sha256:p,channel:r}}finally{try{Ze(c,{recursive:!0,force:!0})}catch{}}}function gr(){return{runtimeDir:re,runtimeBin:A,versionFile:oe}}async function et(e={}){let t=await mr({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 hr(){let e=$e();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 fr(){let e=G(),t=gr();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 wr(e={}){let t=$e(),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 et({channel:r,baseUrl:e.baseUrl,force:!0})}import{existsSync as Rn,mkdirSync as Tn}from"node:fs";import{resolve as $n}from"node:path";import y from"picocolors";import{CloudFileTransport as En}from"@shogo-ai/sdk/cloud-file-transport";async function yr(e,t){if(!e)throw Error("projectId is required");let r=M({apiKey:t.apiKey,cloudUrl:t.cloudUrl});it(r.projectsDir);let o=$n(t.into??Y(e,r.projectsDir));if(!Rn(o))Tn(o,{recursive:!0});let n=t.include?.split(",").map((a)=>a.trim()).filter(Boolean);if(console.log(y.bold(`
20
+ `,{mode:384}),t.log(`[runtime install] Installed agent-runtime ${i} (${n}) to ${A}`),{version:i,target:n,binPath:A,source:a.tarball,sha256:p,channel:r}}finally{try{Ze(c,{recursive:!0,force:!0})}catch{}}}function hr(){return{runtimeDir:re,runtimeBin:A,versionFile:oe}}async function et(e={}){let t=await mr({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 gr(){let e=$e();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 fr(){let e=G(),t=hr();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(q()),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 wr(e={}){let t=$e(),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 et({channel:r,baseUrl:e.baseUrl,force:!0})}import{existsSync as Rn,mkdirSync as Tn}from"node:fs";import{resolve as $n}from"node:path";import y from"picocolors";import{CloudFileTransport as En}from"@shogo-ai/sdk/cloud-file-transport";async function yr(e,t){if(!e)throw Error("projectId is required");let r=M({apiKey:t.apiKey,cloudUrl:t.cloudUrl});it(r.projectsDir);let o=$n(t.into??Y(e,r.projectsDir));if(!Rn(o))Tn(o,{recursive:!0});let n=t.include?.split(",").map((a)=>a.trim()).filter(Boolean);if(console.log(y.bold(`
21
21
  shogo project pull ${y.cyan(e)}`)),console.log(y.dim(" cloud ")+r.cloudUrl),console.log(y.dim(" into ")+o),n?.length)console.log(y.dim(" include ")+n.join(", "));console.log("");let i=new En({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,include:n,onProgress:xn()}),s=await i.downloadAll();if(In("Pull",s),t.watch){console.log(y.bold(`
22
22
  Watching for local changes...`));let a=new ae({rootDir:o,transport:i,onFlush:({uploaded:u,errors:p})=>{let m=u.length===1?u[0]:`${u.length} files`,f=p>0?y.red(` (${p} errors)`):"";console.log(y.dim(` ↑ ${m}${f}`))}});a.start();let c=!1,l=async(u)=>{if(c)return;c=!0,console.log(y.dim(`
23
23
  Received ${u} — flushing pending uploads...`));try{await a.stop()}catch(p){console.warn(y.yellow(`watcher.stop: ${p?.message??p}`))}process.exit(0)};process.once("SIGINT",()=>void l("SIGINT")),process.once("SIGTERM",()=>void l("SIGTERM")),process.once("SIGHUP",()=>void l("SIGHUP")),await new Promise(()=>{})}}function xn(){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?y.dim(` (${_n(e.bytes)})`):"";console.log(y.dim(` ${t}${r} ${e.path}${o}`))}}function In(e,t){let r=t.errors.length===0,o=r?y.green(`✓ ${e} complete`):y.red(`✗ ${e} completed with errors`);if(console.log(`
24
24
  ${o}`),console.log(y.dim(" downloaded: ")+t.downloaded),t.uploaded)console.log(y.dim(" uploaded: ")+t.uploaded);if(t.deleted)console.log(y.dim(" deleted: ")+t.deleted);if(t.skipped)console.log(y.dim(" skipped: ")+t.skipped);if(!r){console.log(y.dim(" errors: ")+t.errors.length);for(let n of t.errors.slice(0,5))console.log(y.dim(" ")+y.red(`${n.path}: ${n.message}`));if(t.errors.length>5)console.log(y.dim(` ... and ${t.errors.length-5} more`))}}function _n(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 Cn}from"node:fs";import{resolve as On}from"node:path";import k from"picocolors";import{CloudFileTransport as An}from"@shogo-ai/sdk/cloud-file-transport";async function vr(e,t){if(!e)throw Error("projectId is required");let r=M({apiKey:t.apiKey,cloudUrl:t.cloudUrl}),o=On(t.from??Y(e,r.projectsDir));if(!Cn(o))throw Error(`Source directory does not exist: ${o}`);let n=t.include?.split(",").map((a)=>a.trim()).filter(Boolean);if(console.log(k.bold(`
25
25
  shogo project push ${k.cyan(e)}`)),console.log(k.dim(" cloud ")+r.cloudUrl),console.log(k.dim(" from ")+o),n?.length)console.log(k.dim(" include ")+n.join(", "));if(t.deleteRemote)console.log(k.yellow(" --delete-remote: remote files not present locally will be DELETED"));console.log("");let s=await new An({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,include:n,onProgress:(a)=>{let c=a.kind==="upload"?"↑":a.kind==="delete"?"✗":"·",l=a.total>0?` ${a.index+1}/${a.total}`:"",u=a.bytes!=null?k.dim(` (${Wn(a.bytes)})`):"";console.log(k.dim(` ${c}${l} ${a.path}${u}`))}}).uploadAll({deleteRemote:t.deleteRemote});Mn("Push",s)}function Mn(e,t){let r=t.errors.length===0,o=r?k.green(`✓ ${e} complete`):k.red(`✗ ${e} completed with errors`);if(console.log(`
26
- ${o}`),console.log(k.dim(" uploaded: ")+t.uploaded),t.deleted)console.log(k.dim(" deleted: ")+t.deleted);if(!r){console.log(k.dim(" errors: ")+t.errors.length);for(let n of t.errors.slice(0,5))console.log(k.dim(" ")+k.red(`${n.path}: ${n.message}`));if(t.errors.length>5)console.log(k.dim(` ... and ${t.errors.length-5} more`))}}function Wn(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 Dn}from"node:fs";import{resolve as Un}from"node:path";import D from"picocolors";Se();async function br(e,t){if(!e)throw Error("projectId is required");let r=M({apiKey:t.apiKey,cloudUrl:t.cloudUrl}),o=Un(t.into??Y(e,r.projectsDir));if(!Dn(o))throw Error(`Local project dir does not exist: ${o} — run \`shogo project pull\` first`);if(!be(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(D.bold(`
27
- shogo project checkout ${D.cyan(e)}`)),console.log(D.dim(" cloud ")+r.cloudUrl),console.log(D.dim(" local ")+o),t.at)console.log(D.dim(" at ")+t.at);if(console.log(""),t.unshallow)console.log(D.dim("Unshallowing repo (this may take a moment)...")),await je({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o});if(!t.at){let i=await ve({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o});console.log(D.green(`✓ Reset to ${i.commitSha.slice(0,8)} (remote HEAD)`));return}let n;try{n=(await E(["rev-parse","--verify",`${t.at}^{commit}`],{cwd:o})).stdout.trim()}catch{n=await Nn(r.cloudUrl,r.apiKey,e,t.at),console.log(D.dim(` resolved checkpoint "${t.at}" → ${n.slice(0,8)}`))}try{await ve({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,branch:n})}catch{if(!t.unshallow)console.log(D.yellow(" fetch failed; unshallowing and retrying...")),await je({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o}),await ve({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,branch:n});else throw Error(`Cannot reach commit ${n} in local clone`)}await E(["reset","--hard",n],{cwd:o}),console.log(D.green(`✓ Checked out ${n.slice(0,8)}`))}async function Nn(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(),c=s.checkpoints.find((l)=>{if(l.name&&l.name.toLowerCase()===a)return!0;if(l.commitSha.startsWith(o))return!0;if(l.commitMessage&&l.commitMessage.toLowerCase().includes(a))return!0;return!1});if(!c)throw Error(`No checkpoint matches "${o}" (searched ${s.checkpoints.length} most recent)`);return c.commitSha}import{createInterface as Gn}from"node:readline";import v from"picocolors";W();import{existsSync as Q,copyFileSync as Sr}from"node:fs";import O from"node:path";import{homedir as kr,platform as Pr}from"node:os";function Rr(e,t,r){let n=Ae(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 Ee(e,t){if(!Q(t))return[];return Rr(e,t,`
26
+ ${o}`),console.log(k.dim(" uploaded: ")+t.uploaded),t.deleted)console.log(k.dim(" deleted: ")+t.deleted);if(!r){console.log(k.dim(" errors: ")+t.errors.length);for(let n of t.errors.slice(0,5))console.log(k.dim(" ")+k.red(`${n.path}: ${n.message}`));if(t.errors.length>5)console.log(k.dim(` ... and ${t.errors.length-5} more`))}}function Wn(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 Dn}from"node:fs";import{resolve as Un}from"node:path";import D from"picocolors";be();async function Sr(e,t){if(!e)throw Error("projectId is required");let r=M({apiKey:t.apiKey,cloudUrl:t.cloudUrl}),o=Un(t.into??Y(e,r.projectsDir));if(!Dn(o))throw Error(`Local project dir does not exist: ${o} — run \`shogo project pull\` first`);if(!Se(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(D.bold(`
27
+ shogo project checkout ${D.cyan(e)}`)),console.log(D.dim(" cloud ")+r.cloudUrl),console.log(D.dim(" local ")+o),t.at)console.log(D.dim(" at ")+t.at);if(console.log(""),t.unshallow)console.log(D.dim("Unshallowing repo (this may take a moment)...")),await je({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o});if(!t.at){let i=await ve({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o});console.log(D.green(`✓ Reset to ${i.commitSha.slice(0,8)} (remote HEAD)`));return}let n;try{n=(await E(["rev-parse","--verify",`${t.at}^{commit}`],{cwd:o})).stdout.trim()}catch{n=await Nn(r.cloudUrl,r.apiKey,e,t.at),console.log(D.dim(` resolved checkpoint "${t.at}" → ${n.slice(0,8)}`))}try{await ve({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,branch:n})}catch{if(!t.unshallow)console.log(D.yellow(" fetch failed; unshallowing and retrying...")),await je({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o}),await ve({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,branch:n});else throw Error(`Cannot reach commit ${n} in local clone`)}await E(["reset","--hard",n],{cwd:o}),console.log(D.green(`✓ Checked out ${n.slice(0,8)}`))}async function Nn(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(),c=s.checkpoints.find((l)=>{if(l.name&&l.name.toLowerCase()===a)return!0;if(l.commitSha.startsWith(o))return!0;if(l.commitMessage&&l.commitMessage.toLowerCase().includes(a))return!0;return!1});if(!c)throw Error(`No checkpoint matches "${o}" (searched ${s.checkpoints.length} most recent)`);return c.commitSha}import{createInterface as Gn}from"node:readline";import v from"picocolors";W();import{existsSync as Q,copyFileSync as br}from"node:fs";import O from"node:path";import{homedir as kr,platform as Pr}from"node:os";function Rr(e,t,r){let n=Ae(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 Ee(e,t){if(!Q(t))return[];return Rr(e,t,`
28
28
  import { Database } from 'bun:sqlite';
29
29
  try {
30
30
  const db = new Database(process.env.DBP, { readonly: true });
@@ -38,7 +38,7 @@ shogo project checkout ${D.cyan(e)}`)),console.log(D.dim(" cloud ")+r.cloudUrl
38
38
  console.error(String(e?.stack || e));
39
39
  process.exit(2);
40
40
  }
41
- `)}function jn(e){if(!Q(e))throw Error(`Database does not exist at ${e} — refusing to back up nothing`);let t=O.dirname(e),r=O.basename(e),o=new Date().toISOString().replace(/[:.]/g,"-"),n=O.join(t,`${r}.bak-${o}`);Sr(e,n);for(let i of["-wal","-shm"]){let s=`${e}${i}`;if(Q(s))Sr(s,`${n}${i}`)}return n}function Fn(e,t,r){if(r.length===0)return 0;if(!Q(t))throw Error(`Cannot repair: database does not exist at ${t}`);let n=`
41
+ `)}function jn(e){if(!Q(e))throw Error(`Database does not exist at ${e} — refusing to back up nothing`);let t=O.dirname(e),r=O.basename(e),o=new Date().toISOString().replace(/[:.]/g,"-"),n=O.join(t,`${r}.bak-${o}`);br(e,n);for(let i of["-wal","-shm"]){let s=`${e}${i}`;if(Q(s))br(s,`${n}${i}`)}return n}function Fn(e,t,r){if(r.length===0)return 0;if(!Q(t))throw Error(`Cannot repair: database does not exist at ${t}`);let n=`
42
42
  import { Database } from 'bun:sqlite';
43
43
  const names = ${JSON.stringify(r)};
44
44
  if (!Array.isArray(names) || names.some(n => typeof n !== 'string')) {
@@ -60,5 +60,5 @@ shogo project checkout ${D.cyan(e)}`)),console.log(D.dim(" cloud ")+r.cloudUrl
60
60
  `;return Rr(e,t,n).deleted}function Tr(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=Ee(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((p)=>p.name).join(", ")}`);let i;if(!e.skipBackup)i=jn(o),t(`Backed up database to ${i}`);let s=n.map((p)=>p.name),a=Fn(r,o,s);t(`Cleared ${a} failed migration row(s).`);let c=Ee(r,o),l=c.length===0?"repaired":"failed",u=l==="repaired"?"Cleared the failed migration record. Relaunch Shogo to re-apply migrations cleanly.":`Repair incomplete — ${c.length} migration(s) still failed: ${c.map((p)=>p.name).join(", ")}.`;return{status:l,detected:n,backupPath:i,cleared:s.slice(0,a),remaining:c,message:u}}function Hn(){let e=kr(),t=Pr(),r;if(t==="darwin")r=O.join(e,"Library","Application Support");else if(t==="win32")r=process.env.APPDATA??O.join(e,"AppData","Roaming");else r=process.env.XDG_CONFIG_HOME??O.join(e,".config");return O.join(r,"Shogo","data")}function $r(){return O.join(Hn(),"shogo.db")}function Ln(){let e=Pr(),t=e==="win32"?"bun.exe":"bun",r=kr(),o=[];if(e==="darwin")o.push(O.join("/Applications","Shogo.app","Contents","Resources","bun",t),O.join(r,"Applications","Shogo.app","Contents","Resources","bun",t));else if(e==="linux")o.push(O.join("/opt","Shogo","resources","bun",t),O.join("/usr","lib","shogo","resources","bun",t));return o}function tt(e){try{return Ae(e,["--version"],{stdio:"ignore",timeout:5000}),!0}catch{return!1}}function Er(e){if(e)return tt(e)?e:null;if(process.versions.bun&&process.execPath)return process.execPath;for(let t of Ln())if(Q(t)&&tt(t))return t;if(tt("bun"))return"bun";return null}function Bn(e){for(let t of e){let r=Number.isFinite(t.startedAt)?new Date(t.startedAt).toISOString():"unknown time",o=(t.errorExcerpt??"").split(`
61
61
  `)[0]?.trim();if(console.log(` ${v.yellow("•")} ${v.bold(t.name)} ${v.dim(`(attempted ${r})`)}`),o)console.log(` ${v.dim(o)}`)}}async function Kn(e){if(!process.stdin.isTTY)return console.log(v.dim("(stdin is not a TTY — re-run with --yes to repair non-interactively)")),!1;let t=Gn({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 xr(e={}){let t=e.db??$r(),r=Er(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
62
62
  `+" (e.g. /Applications/Shogo.app/Contents/Resources/bun/bun on macOS).");console.log(v.dim(`Database: ${t}`)),console.log(v.dim(`Bun: ${r}`)),console.log();let o=Ee(r,t);if(o.length===0){console.log(v.green("✓ No failed migrations detected — your local database looks healthy."));return}if(console.log(v.red(`✗ Found ${o.length} failed migration(s):`)),Bn(o),console.log(),e.check){console.log(v.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?v.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 Kn(v.bold("Clear these failed migration records and repair? [y/N] "))){console.log(v.dim("Aborted — no changes made.")),process.exitCode=1;return}console.log()}let n=Tr({bunPath:r,dbPath:t,skipBackup:e.backup===!1,log:(i)=>console.log(v.dim(` ${i}`))});if(console.log(),n.status==="repaired"){if(console.log(v.green(`✓ ${n.message}`)),n.backupPath)console.log(v.dim(` Backup: ${n.backupPath}`));console.log(v.bold(`
63
- → Relaunch the Shogo app to finish applying migrations.`))}else{if(console.log(v.red(`✗ ${n.message}`)),n.backupPath)console.log(v.dim(` Backup: ${n.backupPath}`));process.exitCode=1}}W();import{resolve as qn}from"node:path";import Ir from"picocolors";function Jn(e){let{flags:t,config:r,runtime:o}=e,n=t.cwd?qn(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 _r(e={}){let t=M({apiKey:e.apiKey,cloudUrl:e.cloudUrl}),r=G({flag:e.runtimeBin});if(!r)console.error(Ir.red(q({flag:e.runtimeBin}))),process.exit(1);let o=Jn({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(Ir.red(`✗ Failed to launch agent-runtime: ${a.message}`)),i(1)}),s.on("exit",(a)=>i(a??0))});process.exit(n)}var Yn="0.1.0",U=new Vn;U.name("shogo").description("Shogo Cloud Agent Worker — run Shogo agents on your own machine.").version(Yn);U.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)=>P(()=>sr(e)));var xe=U.command("worker").description("Manage the local worker process");xe.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)=>P(()=>Qt(e)));xe.command("stop").description("Stop the running worker").action(()=>P(er));xe.command("status").description("Show worker status").action(()=>P(tr));xe.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)=>P(()=>or(e)));var Ie=U.command("runtime").description("Install / inspect the local agent-runtime binary");Ie.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)=>P(()=>et(e)));Ie.command("version").description("Print the installed agent-runtime version").action(()=>P(()=>hr()));Ie.command("where").description("Print the resolved agent-runtime binary path").action(()=>P(()=>fr()));Ie.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)=>P(()=>wr(e)));var Or=U.command("config").description("Inspect or modify ~/.shogo/config.json");Or.command("show").description("Print current config (API key masked)").action(()=>P(ar));Or.command("set <key> <value>").description("Set apiKey / cloudUrl / name / workerDir / port / projectsDir").action((e,t)=>P(()=>lr(e,t)));var rt=U.command("project").description("Clone/sync project workspaces between Shogo Cloud and this machine");rt.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)=>P(()=>yr(e,t)));rt.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)=>P(()=>vr(e,t)));rt.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)=>P(()=>br(e,t)));U.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)=>P(()=>xr(e)));U.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)=>P(()=>_r({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})));U.showHelpAfterError(Cr.dim(`
63
+ → Relaunch the Shogo app to finish applying migrations.`))}else{if(console.log(v.red(`✗ ${n.message}`)),n.backupPath)console.log(v.dim(` Backup: ${n.backupPath}`));process.exitCode=1}}W();import{resolve as qn}from"node:path";import Ir from"picocolors";function Jn(e){let{flags:t,config:r,runtime:o}=e,n=t.cwd?qn(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 _r(e={}){let t=M({apiKey:e.apiKey,cloudUrl:e.cloudUrl}),r=G({flag:e.runtimeBin});if(!r)console.error(Ir.red(q({flag:e.runtimeBin}))),process.exit(1);let o=Jn({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(Ir.red(`✗ Failed to launch agent-runtime: ${a.message}`)),i(1)}),s.on("exit",(a)=>i(a??0))});process.exit(n)}var Yn="0.1.0",U=new Vn;U.name("shogo").description("Shogo Cloud Agent Worker — run Shogo agents on your own machine.").version(Yn);U.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)=>P(()=>sr(e)));var xe=U.command("worker").description("Manage the local worker process");xe.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)=>P(()=>Qt(e)));xe.command("stop").description("Stop the running worker").action(()=>P(er));xe.command("status").description("Show worker status").action(()=>P(tr));xe.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)=>P(()=>or(e)));var Ie=U.command("runtime").description("Install / inspect the local agent-runtime binary");Ie.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)=>P(()=>et(e)));Ie.command("version").description("Print the installed agent-runtime version").action(()=>P(()=>gr()));Ie.command("where").description("Print the resolved agent-runtime binary path").action(()=>P(()=>fr()));Ie.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)=>P(()=>wr(e)));var Or=U.command("config").description("Inspect or modify ~/.shogo/config.json");Or.command("show").description("Print current config (API key masked)").action(()=>P(ar));Or.command("set <key> <value>").description("Set apiKey / cloudUrl / name / workerDir / port / projectsDir").action((e,t)=>P(()=>lr(e,t)));var rt=U.command("project").description("Clone/sync project workspaces between Shogo Cloud and this machine");rt.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)=>P(()=>yr(e,t)));rt.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)=>P(()=>vr(e,t)));rt.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)=>P(()=>Sr(e,t)));U.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)=>P(()=>xr(e)));U.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)=>P(()=>_r({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})));U.showHelpAfterError(Cr.dim(`
64
64
  (use --help for usage)`));U.parseAsync(process.argv);async function P(e){try{await e()}catch(t){if(console.error(Cr.red(`✗ ${t?.message??t}`)),process.env.SHOGO_DEBUG)console.error(t?.stack);process.exit(1)}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shogo-ai/worker",
3
- "version": "2.3.4",
3
+ "version": "2.3.5",
4
4
  "description": "Shogo Cloud Agent Worker — run Shogo agents on your own machine (laptop, devbox, CI).",
5
5
  "license": "MIT",
6
6
  "author": "Shogo Technologies, Inc.",
@@ -371,6 +371,40 @@ describe('collectMetadata() (L259-286)', () => {
371
371
  expect(Array.isArray(captured.metadata.projects)).toBe(true);
372
372
  });
373
373
 
374
+ it('carries the owner-gated machine snapshot when the resolver provides one', async () => {
375
+ let captured: any = null;
376
+ global.fetch = ((_url: string, init?: RequestInit) => {
377
+ captured = JSON.parse(init?.body as string ?? '{}');
378
+ return fakeHeartbeatResp() as any;
379
+ }) as any;
380
+ const snapshot = {
381
+ machine: { id: 'device-1', hostname: 'mac' },
382
+ workspaces: [{ id: 'lw-1', name: 'Personal', projectCount: 3 }],
383
+ activity: { runningTurns: 1 },
384
+ };
385
+ const t = makeTunnel({
386
+ resolver: makeResolver({ machineSnapshot: async () => snapshot }),
387
+ });
388
+ await t._testing().sendHeartbeat();
389
+ t.stop();
390
+ expect(captured.metadata.machine).toEqual(snapshot);
391
+ });
392
+
393
+ it('still heartbeats when the machine snapshot fails', async () => {
394
+ let captured: any = null;
395
+ global.fetch = ((_url: string, init?: RequestInit) => {
396
+ captured = JSON.parse(init?.body as string ?? '{}');
397
+ return fakeHeartbeatResp() as any;
398
+ }) as any;
399
+ const t = makeTunnel({
400
+ resolver: makeResolver({ machineSnapshot: async () => { throw new Error('db down'); } }),
401
+ });
402
+ await t._testing().sendHeartbeat();
403
+ t.stop();
404
+ expect(captured.metadata.machine).toBeUndefined();
405
+ expect(captured.metadata.protocolVersion).toBe(TUNNEL_PROTOCOL_VERSION);
406
+ });
407
+
374
408
  it('handles resolver.getActiveProjects() throwing', async () => {
375
409
  let captured: any = null;
376
410
  global.fetch = ((_url: string, init?: RequestInit) => {
package/src/lib/tunnel.ts CHANGED
@@ -78,6 +78,14 @@ export interface RuntimeResolver {
78
78
  * tunnel falls back to a generic `NO_LOCAL_RUNTIME` payload.
79
79
  */
80
80
  describeRejection?(pathWithQuery: string, projectId?: string): ResolveRejection;
81
+
82
+ /**
83
+ * Owner-gated snapshot of this machine's own workspaces (ids, names,
84
+ * counts — never transcripts). Carried on every heartbeat so the cloud
85
+ * can list a machine's workspaces, and show its last-known state while
86
+ * it is offline, without persisting anything machine-specific.
87
+ */
88
+ machineSnapshot?(): Promise<Record<string, unknown> | null> | Record<string, unknown> | null;
81
89
  }
82
90
 
83
91
  interface TunnelRequest {
@@ -285,6 +293,12 @@ export class WorkerTunnel {
285
293
  } catch {
286
294
  meta.activeProjects = 0;
287
295
  }
296
+ try {
297
+ const machine = await this.opts.resolver.machineSnapshot?.();
298
+ if (machine) meta.machine = machine;
299
+ } catch {
300
+ // Best-effort: a stale snapshot is better than no heartbeat at all.
301
+ }
288
302
  return meta;
289
303
  }
290
304