@shogo-ai/worker 2.3.6-beta.20261020t083308 → 2.3.7

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 Mr}from"node:module";var it=(e,t,r)=>()=>{if(e)try{t=e(e=0)}catch(o){r=[o]}if(r)throw r[0];return t};var Ce=Mr(import.meta.url);import*as R from"node:child_process";import{promisify as ut}from"node:util";function pt(e){return!!e&&typeof e==="object"&&!Array.isArray(e)}function dt(e){if(e.windowsHide!==void 0)return e;return{...e,windowsHide:!0}}function mt(e){let t=e.slice(),r=t[1];if(t.length<2||typeof r==="function")return t.splice(1,0,{windowsHide:!0}),t;if(r==null)return t[1]={windowsHide:!0},t;if(pt(r))return t[1]=dt(r),t;return t}function ne(e){let t=e.slice(),r=-1;for(let i=1;i<t.length;i++)if(typeof t[i]==="function"){r=i;break}let o=r===-1?t.length:r;for(let i=1;i<o;i++)if(pt(t[i]))return t[i]=dt(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,ct,{enumerable:!1,configurable:!0,writable:!1,value(...o){let n=t(),i=n[ct];if(typeof i==="function")return Reflect.apply(i,n,e(o));let s=ut(n);return Reflect.apply(s,this,e(o))}}),r}var ct,T,ge,gi,hi,he,Me,fi;var W=it(()=>{ct=ut.custom;T=K(ne,()=>R.spawn),ge=K(ne,()=>R.spawnSync),gi=K(mt,()=>R.exec),hi=K(mt,()=>R.execSync),he=K(ne,()=>R.execFile),Me=K(ne,()=>R.execFileSync),fi=K(ne,()=>R.fork)});import{existsSync as je}from"node:fs";import{join as Fe}from"node:path";import{promisify as go}from"node:util";async function Ot(e=!1){if(ie!==null&&!e)return ie;try{await ho("git",["--version"],{timeout:3000}),ie=!0}catch{ie=!1}return ie}function Se(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 l=T("git",e,{cwd:r,env:{...process.env,...o},stdio:["ignore","pipe","pipe"]}),c="",u="",p=[],m=[];l.stdout.setEncoding("utf-8"),l.stderr.setEncoding("utf-8"),l.stdout.on("data",(d)=>{if(p.push(d),i)i.log(d.trimEnd())}),l.stderr.on("data",(d)=>{if(m.push(d),i)i.warn(d.trimEnd())});let f=setTimeout(()=>{try{l.kill("SIGKILL")}catch{}a(Error(`git ${e[0]} timed out after ${n}ms`))},n);l.on("error",(d)=>{clearTimeout(f),a(d)}),l.on("close",(d)=>{clearTimeout(f),c=p.join(""),u=m.join("");let w=d??-1;if(w===0)s({stdout:c,stderr:u,exitCode:w});else{let g=Error(`git ${e[0]} exited with code ${w}: ${u.slice(0,500)}`);g.exitCode=w,g.stdout=c,g.stderr=u,a(g)}})})}async function At(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,shallow:i=!0,logger:s,timeoutMs:a}=e;if(je(Fe(n,".git")))throw Error(`cloneProject: ${n}/.git already exists; use gitFetchAndReset instead`);let l=Se(t,o),c=["-c",`http.extraHeader=Authorization: Bearer ${r}`,"clone"];if(i)c.push("--depth=1");return c.push(l,n),await E(c,{logger:s,timeoutMs:a}),{commitSha:(await E(["rev-parse","HEAD"],{cwd:n,logger:s,timeoutMs:1e4})).stdout.trim()}}async function be(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,branch:i="HEAD",logger:s,timeoutMs:a}=e,l=Se(t,o),c=["-c",`http.extraHeader=Authorization: Bearer ${r}`];return await E([...c,"fetch",l,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 He(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,logger:i,timeoutMs:s}=e,a=Se(t,o),l=["-c",`http.extraHeader=Authorization: Bearer ${r}`];if(je(Fe(n,".git","shallow")))await E([...l,"fetch","--unshallow",a],{cwd:n,logger:i,timeoutMs:s})}async function fo(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,message:i,branch:s="HEAD",authorEmail:a,authorName:l,logger:c,timeoutMs:u}=e,p=Se(t,o),m={};if(a)m.GIT_AUTHOR_EMAIL=a,m.GIT_COMMITTER_EMAIL=a;if(l)m.GIT_AUTHOR_NAME=l,m.GIT_COMMITTER_NAME=l;await E(["add","-A"],{cwd:n,env:m,logger:c,timeoutMs:u});let f=!1;try{await E(["diff","--cached","--quiet"],{cwd:n,env:m,logger:c,timeoutMs:u})}catch{f=!0}if(!f)return{committed:!1};await E(["commit","-m",i,"--no-verify","--trailer",`Co-authored-by: ${l||process.env.SHOGO_AGENT_GIT_NAME||"Shogo Agent"} <${a||process.env.SHOGO_AGENT_GIT_EMAIL||"agent@shogo.ai"}>`],{cwd:n,env:m,logger:c,timeoutMs:u});let w=(await E(["rev-parse","HEAD"],{cwd:n,env:m,logger:c,timeoutMs:1e4})).stdout.trim(),g=["-c",`http.extraHeader=Authorization: Bearer ${r}`];return await E([...g,"push",p,s],{cwd:n,env:m,logger:c,timeoutMs:u}),{committed:!0,commitSha:w}}function ve(e){return je(Fe(e,".git"))}var ho,ie=null;var ke=it(()=>{W();ho=go(he)});import{Command as ei}from"commander";import Or from"picocolors";import h from"picocolors";import{existsSync as Vo}from"node:fs";import{readFileSync as Dr,writeFileSync as Ur,existsSync as Nr,chmodSync as jr}from"node:fs";import{hostname as Fr}from"node:os";import{homedir as Wr}from"node:os";import{join as I}from"node:path";import{mkdirSync as ue}from"node:fs";var j=I(Wr(),".shogo"),F=I(j,"config.json"),ai=I(j,"credentials.json"),Z=I(j,"device-id"),ee=I(j,"worker.pid"),Oe=I(j,"logs"),pe=I(Oe,"worker.log"),de=I(Oe,"worker.err.log"),te=I(j,"runtime"),A=I(te,process.platform==="win32"?"agent-runtime.exe":"agent-runtime"),re=I(te,"version.json"),me=I(j,"projects");function Y(e,t=me){return I(t,e)}function H(){ue(j,{recursive:!0,mode:448}),ue(Oe,{recursive:!0,mode:448})}function st(){H(),ue(te,{recursive:!0,mode:448})}function at(e=me){H(),ue(e,{recursive:!0,mode:448})}var Ae={cloudUrl:"https://studio.shogo.ai",port:8002,projectsDir:me};function L(){if(!Nr(F))return{};try{return JSON.parse(Dr(F,"utf-8"))}catch(e){throw Error(`Corrupt config at ${F}: ${e.message}`)}}function oe(e){H(),Ur(F,JSON.stringify(e,null,2),{mode:384}),jr(F,384)}function lt(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=lt(lt(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??Ae.cloudUrl,name:o.name??Fr(),workerDir:o.workerDir??process.cwd(),port:o.port??Ae.port,projectsDir:o.projectsDir??Ae.projectsDir}}W();import{existsSync as Hr,openSync as gt,readFileSync as Lr,unlinkSync as Gr,writeFileSync as Br}from"node:fs";function fe(){if(!Hr(ee))return null;let e=Lr(ee,"utf-8").trim(),t=parseInt(e,10);return Number.isFinite(t)?t:null}function we(e){try{return process.kill(e,0),!0}catch{return!1}}function We(){try{Gr(ee)}catch{}}function ht(e){H();let t=fe();if(t&&we(t))throw Error(`Worker already running (pid=${t}). Run \`shogo worker stop\` first.`);if(t)We();let r=e.inheritStdio?["ignore","inherit","inherit"]:["ignore",gt(pe,"a"),gt(de,"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(Br(ee,String(o.pid),{mode:384}),e.detach)o.unref();return{pid:o.pid,child:o}}function ft(e="SIGTERM"){let t=fe();if(!t)return{killedPid:null};if(!we(t))return We(),{killedPid:null};try{process.kill(t,e)}catch{}return We(),{killedPid:t}}import _ from"picocolors";import{existsSync as Jr}from"node:fs";import{request as Kr}from"node:http";function yt(e,t=process.env){let r=(i)=>i&&i.trim().length>0?i.trim():void 0,o=r(e);if(o)return{url:wt(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:wt(a),source:i}}return null}function wt(e){if(/^https?:\/\//i.test(e))return e;return`http://${e}`}function De(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 St(e,t="api.shogo.ai",r=5000){return new Promise((o)=>{let n=!1,i=(s)=>{if(n)return;n=!0,o(s)};try{let s=new URL(e.url),a=s.hostname,l=s.port?parseInt(s.port,10):s.protocol==="https:"?443:80,c=Kr({method:"CONNECT",host:a,port:l,path:`${t}:443`,timeout:r});c.on("connect",(u)=>{c.destroy();let p=u.statusCode??0;if(p===200)i({ok:!0,detail:`CONNECT ${t}:443 → 200 (via ${a}:${l})`});else if(p===407)i({ok:!1,detail:`407 Proxy Authentication Required — check credentials in ${e.source}`});else i({ok:!1,detail:`CONNECT → HTTP ${p}`})}),c.on("timeout",()=>{c.destroy(),i({ok:!1,detail:`timeout after ${r}ms`})}),c.on("error",(u)=>{let p=u.code?` (${u.code})`:"";i({ok:!1,detail:`${u.message}${p}`})}),c.end()}catch(s){i({ok:!1,detail:s?.message??"unknown error"})}})}function bt(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 Vr(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 vt=(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 Jr(e.workerDir)?{ok:!0,detail:e.workerDir}:{ok:!1,detail:`${e.workerDir} does not exist`}}}];if(e.proxy)t.push({name:`Proxy reachable (${Yr(e.proxy.url)})`,criticality:"fatal",async run(){return St(e.proxy)}});let r=bt(e.cloudUrl);for(let o of r)t.push({name:`Reach ${o.host}${o.purpose!=="control"?_.dim(` (${o.purpose})`):""}`,criticality:o.criticality,run:()=>Vr(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 Yr(e){try{return new URL(e).host}catch{return e}}async function kt(e){console.log(_.bold(`
1
+ import{createRequire as Dr}from"node:module";var at=(e,t,r)=>()=>{if(e)try{t=e(e=0)}catch(o){r=[o]}if(r)throw r[0];return t};var Oe=Dr(import.meta.url);import*as R from"node:child_process";import{promisify as dt}from"node:util";function mt(e){return!!e&&typeof e==="object"&&!Array.isArray(e)}function gt(e){if(e.windowsHide!==void 0)return e;return{...e,windowsHide:!0}}function ht(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(mt(r))return t[1]=gt(r),t;return t}function ne(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(mt(t[i]))return t[i]=gt(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,pt,{enumerable:!1,configurable:!0,writable:!1,value(...o){let n=t(),i=n[pt];if(typeof i==="function")return Reflect.apply(i,n,e(o));let s=dt(n);return Reflect.apply(s,this,e(o))}}),r}var pt,T,ge,wi,yi,he,Me,Si;var W=at(()=>{pt=dt.custom;T=K(ne,()=>R.spawn),ge=K(ne,()=>R.spawnSync),wi=K(ht,()=>R.exec),yi=K(ht,()=>R.execSync),he=K(ne,()=>R.execFile),Me=K(ne,()=>R.execFileSync),Si=K(ne,()=>R.fork)});import{existsSync as He}from"node:fs";import{join as Le}from"node:path";import{promisify as fo}from"node:util";async function Mt(e=!1){if(ie!==null&&!e)return ie;try{await wo("git",["--version"],{timeout:3000}),ie=!0}catch{ie=!1}return ie}function Se(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 l=T("git",e,{cwd:r,env:{...process.env,...o},stdio:["ignore","pipe","pipe"]}),c="",u="",p=[],g=[];l.stdout.setEncoding("utf-8"),l.stderr.setEncoding("utf-8"),l.stdout.on("data",(d)=>{if(p.push(d),i)i.log(d.trimEnd())}),l.stderr.on("data",(d)=>{if(g.push(d),i)i.warn(d.trimEnd())});let f=setTimeout(()=>{try{l.kill("SIGKILL")}catch{}a(Error(`git ${e[0]} timed out after ${n}ms`))},n);l.on("error",(d)=>{clearTimeout(f),a(d)}),l.on("close",(d)=>{clearTimeout(f),c=p.join(""),u=g.join("");let w=d??-1;if(w===0)s({stdout:c,stderr:u,exitCode:w});else{let m=Error(`git ${e[0]} exited with code ${w}: ${u.slice(0,500)}`);m.exitCode=w,m.stdout=c,m.stderr=u,a(m)}})})}async function Wt(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,shallow:i=!0,logger:s,timeoutMs:a}=e;if(He(Le(n,".git")))throw Error(`cloneProject: ${n}/.git already exists; use gitFetchAndReset instead`);let l=Se(t,o),c=["-c",`http.extraHeader=Authorization: Bearer ${r}`,"clone"];if(i)c.push("--depth=1");return c.push(l,n),await E(c,{logger:s,timeoutMs:a}),{commitSha:(await E(["rev-parse","HEAD"],{cwd:n,logger:s,timeoutMs:1e4})).stdout.trim()}}async function be(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,branch:i="HEAD",logger:s,timeoutMs:a}=e,l=Se(t,o),c=["-c",`http.extraHeader=Authorization: Bearer ${r}`];return await E([...c,"fetch",l,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 Ge(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,logger:i,timeoutMs:s}=e,a=Se(t,o),l=["-c",`http.extraHeader=Authorization: Bearer ${r}`];if(He(Le(n,".git","shallow")))await E([...l,"fetch","--unshallow",a],{cwd:n,logger:i,timeoutMs:s})}async function So(e){let{apiUrl:t,apiKey:r,projectId:o,localDir:n,message:i,branch:s="HEAD",logger:a,timeoutMs:l}=e,c=Se(t,o),u={GIT_AUTHOR_NAME:je,GIT_AUTHOR_EMAIL:Fe,GIT_COMMITTER_NAME:je,GIT_COMMITTER_EMAIL:Fe};await E(["add","-A"],{cwd:n,env:u,logger:a,timeoutMs:l});let p=!1;try{await E(["diff","--cached","--quiet"],{cwd:n,env:u,logger:a,timeoutMs:l})}catch{p=!0}if(!p)return{committed:!1};await E(["commit","-m",i,"--no-verify","--trailer",yo],{cwd:n,env:u,logger:a,timeoutMs:l});let f=(await E(["rev-parse","HEAD"],{cwd:n,env:u,logger:a,timeoutMs:1e4})).stdout.trim(),d=["-c",`http.extraHeader=Authorization: Bearer ${r}`];return await E([...d,"push",c,s],{cwd:n,env:u,logger:a,timeoutMs:l}),{committed:!0,commitSha:f}}function ve(e){return He(Le(e,".git"))}var wo,je="Shogo Agent",Fe="agent@shogo.ai",yo,ie=null;var ke=at(()=>{W();wo=fo(he),yo=`Co-authored-by: ${je} <${Fe}>`});import{Command as oi}from"commander";import Mr from"picocolors";import h from"picocolors";import{existsSync as zo}from"node:fs";import{readFileSync as Nr,writeFileSync as jr,existsSync as Fr,chmodSync as Hr}from"node:fs";import{hostname as Lr}from"node:os";import{homedir as Ur}from"node:os";import{join as I}from"node:path";import{mkdirSync as ue}from"node:fs";var j=I(Ur(),".shogo"),F=I(j,"config.json"),ui=I(j,"credentials.json"),Z=I(j,"device-id"),ee=I(j,"worker.pid"),Ce=I(j,"logs"),pe=I(Ce,"worker.log"),de=I(Ce,"worker.err.log"),te=I(j,"runtime"),A=I(te,process.platform==="win32"?"agent-runtime.exe":"agent-runtime"),re=I(te,"version.json"),me=I(j,"projects");function Y(e,t=me){return I(t,e)}function H(){ue(j,{recursive:!0,mode:448}),ue(Ce,{recursive:!0,mode:448})}function lt(){H(),ue(te,{recursive:!0,mode:448})}function ct(e=me){H(),ue(e,{recursive:!0,mode:448})}var Ae={cloudUrl:"https://studio.shogo.ai",port:8002,projectsDir:me};function L(){if(!Fr(F))return{};try{return JSON.parse(Nr(F,"utf-8"))}catch(e){throw Error(`Corrupt config at ${F}: ${e.message}`)}}function oe(e){H(),jr(F,JSON.stringify(e,null,2),{mode:384}),Hr(F,384)}function ut(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=ut(ut(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??Ae.cloudUrl,name:o.name??Lr(),workerDir:o.workerDir??process.cwd(),port:o.port??Ae.port,projectsDir:o.projectsDir??Ae.projectsDir}}W();import{existsSync as Gr,openSync as ft,readFileSync as Br,unlinkSync as Kr,writeFileSync as Jr}from"node:fs";function fe(){if(!Gr(ee))return null;let e=Br(ee,"utf-8").trim(),t=parseInt(e,10);return Number.isFinite(t)?t:null}function we(e){try{return process.kill(e,0),!0}catch{return!1}}function We(){try{Kr(ee)}catch{}}function wt(e){H();let t=fe();if(t&&we(t))throw Error(`Worker already running (pid=${t}). Run \`shogo worker stop\` first.`);if(t)We();let r=e.inheritStdio?["ignore","inherit","inherit"]:["ignore",ft(pe,"a"),ft(de,"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(Jr(ee,String(o.pid),{mode:384}),e.detach)o.unref();return{pid:o.pid,child:o}}function yt(e="SIGTERM"){let t=fe();if(!t)return{killedPid:null};if(!we(t))return We(),{killedPid:null};try{process.kill(t,e)}catch{}return We(),{killedPid:t}}import _ from"picocolors";import{existsSync as Vr}from"node:fs";import{request as qr}from"node:http";function bt(e,t=process.env){let r=(i)=>i&&i.trim().length>0?i.trim():void 0,o=r(e);if(o)return{url:St(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:St(a),source:i}}return null}function St(e){if(/^https?:\/\//i.test(e))return e;return`http://${e}`}function De(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 vt(e,t="api.shogo.ai",r=5000){return new Promise((o)=>{let n=!1,i=(s)=>{if(n)return;n=!0,o(s)};try{let s=new URL(e.url),a=s.hostname,l=s.port?parseInt(s.port,10):s.protocol==="https:"?443:80,c=qr({method:"CONNECT",host:a,port:l,path:`${t}:443`,timeout:r});c.on("connect",(u)=>{c.destroy();let p=u.statusCode??0;if(p===200)i({ok:!0,detail:`CONNECT ${t}:443 → 200 (via ${a}:${l})`});else if(p===407)i({ok:!1,detail:`407 Proxy Authentication Required — check credentials in ${e.source}`});else i({ok:!1,detail:`CONNECT → HTTP ${p}`})}),c.on("timeout",()=>{c.destroy(),i({ok:!1,detail:`timeout after ${r}ms`})}),c.on("error",(u)=>{let p=u.code?` (${u.code})`:"";i({ok:!1,detail:`${u.message}${p}`})}),c.end()}catch(s){i({ok:!1,detail:s?.message??"unknown error"})}})}function kt(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 Yr=5000;async function Xr(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 Pt=(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 Vr(e.workerDir)?{ok:!0,detail:e.workerDir}:{ok:!1,detail:`${e.workerDir} does not exist`}}}];if(e.proxy)t.push({name:`Proxy reachable (${zr(e.proxy.url)})`,criticality:"fatal",async run(){return vt(e.proxy)}});let r=kt(e.cloudUrl);for(let o of r)t.push({name:`Reach ${o.host}${o.purpose!=="control"?_.dim(` (${o.purpose})`):""}`,criticality:o.criticality,run:()=>Xr(o.url,Yr)});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 zr(e){try{return new URL(e).host}catch{return e}}async function Rt(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.
@@ -6,24 +6,24 @@ All checks passed.
6
6
  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
- `));return!t}import{accessSync as Xr,constants as zr,existsSync as Qr}from"node:fs";import{delimiter as Zr,join as eo}from"node:path";function G(e={}){let t=e.env??process.env,r=to(e);for(let i of r)if(Rt(i.path))return i;let o=e.systemBinName??Pt(),n=ro(o,t.PATH);if(n)return{path:n,source:"path"};return null}function to(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 Pt(){return process.platform==="win32"?"shogo-agent-runtime.exe":"shogo-agent-runtime"}function Rt(e){if(!Qr(e))return!1;try{if(process.platform==="win32")return!0;return Xr(e,zr.X_OK),!0}catch{return!1}}function ro(e,t){if(!t)return null;for(let r of t.split(Zr)){if(!r)continue;let o=eo(r,e);if(Rt(o))return o}return null}function J(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(` ${Pt()} 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 Jt,randomBytes as Ro}from"node:crypto";import{existsSync as Le,mkdirSync as Wt,readdirSync as qt,rmSync as To}from"node:fs";import{createConnection as $o}from"node:net";import{tmpdir as Dt}from"node:os";import{dirname as Eo,join as q}from"node:path";W();import{existsSync as oo,mkdirSync as no,readFileSync as io,writeFileSync as ye}from"node:fs";import{join as X}from"node:path";var Tt=1e5;function $t(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 Et(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 so(){if(process.platform!=="linux")return!1;try{return ge("systemd-run",["--version"],{stdio:"ignore",timeout:3000}).status===0}catch{return!1}}function xt(e){let{command:t,args:r,limits:o,scopeName:n,logger:i}=e;if(process.platform!=="linux"||!so())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 It(e){let{pid:t,limits:r,logger:o}=e;if(process.platform!=="linux")return!1;let n="/sys/fs/cgroup";if(!oo(X(n,"cgroup.controllers")))return!1;let i=X(n,"shogo",`rt-${t}`);try{if(no(i,{recursive:!0}),ye(X(i,"memory.max"),String(r.memoryMB*1024*1024)),r.cpuPercent>0){let s=Math.floor(r.cpuPercent/100*Tt);ye(X(i,"cpu.max"),`${s} ${Tt}`)}try{ye(X(i,"pids.max"),String(r.tasksMax))}catch{}return ye(X(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 _t(e){let{pid:t,limits:r,logger:o}=e;if(process.platform!=="win32")return!1;let n=process.env.SHOGO_WINJOB_HELPER||"@shogo/winjob";try{let i=Ce(n);if(typeof i.limitProcess!=="function")return!1;let s=i.limitProcess(t,{memoryBytes:r.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 Ct(e){try{let t=JSON.parse(io(e,"utf-8"));if(!Array.isArray(t.pids))return[];return t.pids.filter((r)=>Number.isInteger(r)&&r>0)}catch{return[]}}var ao=20000,lo=`$ErrorActionPreference='SilentlyContinue';foreach($p in Get-CimInstance Win32_Process -Property ProcessId,ParentProcessId,WorkingSetSize){"$($p.ProcessId) $($p.ParentProcessId) $([math]::Floor($p.WorkingSetSize/1024))"}`;function co(e=process.platform){let[t,r]=e==="win32"?["powershell.exe",["-NoProfile","-NonInteractive","-Command",lo]]:["ps",["-A","-o","pid=,ppid=,pgid=,rss="]];return new Promise((o)=>{he(t,r,{encoding:"utf-8",timeout:ao,maxBuffer:8388608},(n,i)=>{if(n||!i){o(null);return}let s=[];for(let a of String(i).split(/\r?\n/)){let l=a.trim().split(/\s+/).map((c)=>parseInt(c,10));if(l.some((c)=>!Number.isFinite(c)))continue;if(e==="win32"&&l.length===3)s.push({pid:l[0],ppid:l[1],pgid:0,kb:l[2]});else if(e!=="win32"&&l.length===4)s.push({pid:l[0],ppid:l[1],pgid:l[2],kb:l[3]})}o(s.length?s:null)})})}function uo(e,t,r=process.platform,o=[]){if(r!=="win32"){let u=e.filter((p)=>p.pgid===t);return u.length?u.reduce((p,m)=>p+m.kb,0):null}if(!e.some((u)=>u.pid===t))return null;let n=new Set(o.filter((u)=>u!==t)),i=new Map;for(let u of e){if(u.pid===u.ppid)continue;let p=i.get(u.ppid);if(p)p.push(u);else i.set(u.ppid,[u])}let s=new Map(e.map((u)=>[u.pid,u])),a=new Set,l=[t],c=0;while(l.length){let u=l.pop();if(a.has(u))continue;a.add(u),c+=s.get(u)?.kb??0;for(let p of i.get(u)??[])if(!n.has(p.pid))l.push(p.pid)}return c}var Ue=new Map;function po(e,t,r){let o=`${t}:${r}`,n=Ue.get(o);if(!n){let i={key:o,watchdogs:new Set,sampling:!1,timer:setInterval(()=>{mo(i,t)},r)};try{i.timer.unref?.()}catch{}Ue.set(o,i),n=i}return n.watchdogs.add(e),n}async function mo(e,t){if(e.sampling)return;e.sampling=!0;try{let r=await co(t);for(let o of[...e.watchdogs])o.observe(r)}finally{e.sampling=!1}}class Ne{sampler=null;consecutive=0;fired=!1;pid;ceilingMB;platform;intervalMs;strikes;excludePids;logger;onBreach;constructor(e){this.pid=e.pid,this.ceilingMB=e.ceilingMB,this.platform=e.platform??process.platform,this.intervalMs=e.intervalMs??parseInt(process.env.RUNTIME_RSS_WATCHDOG_MS||"15000",10),this.strikes=e.strikes??parseInt(process.env.RUNTIME_RSS_WATCHDOG_STRIKES||"2",10),this.excludePids=e.excludePids,this.logger=e.logger,this.onBreach=e.onBreach}start(){if(this.sampler)return;this.sampler=po(this,this.platform,Math.max(2000,this.intervalMs))}stop(){if(this.sampler?.watchdogs.delete(this),this.sampler?.watchdogs.size===0)clearInterval(this.sampler.timer),Ue.delete(this.sampler.key);this.sampler=null}observe(e){if(this.fired)return;let t=e?uo(e,this.pid,this.platform,this.excludePids?.()??[]):null;if(t==null){this.consecutive=0;return}let r=Math.floor(t/1024);if(r<=this.ceilingMB){this.consecutive=0;return}if(this.consecutive+=1,this.logger?.warn(`[resource-limits] pid ${this.pid} RSS ${r}MB over ceiling ${this.ceilingMB}MB (strike ${this.consecutive}/${this.strikes})`),this.consecutive>=this.strikes)this.fired=!0,this.stop(),this.onBreach(r)}}import{CloudFileTransport as Ge}from"@shogo-ai/sdk/cloud-file-transport";import{killViteWatchFromPidfile as Ut}from"@shogo-ai/sdk/vite-watch";import{watch as Mt,statSync as wo}from"node:fs";import{relative as Bi,sep as yo,posix as So}from"node:path";var bo=1500,vo=new Set(["node_modules",".git","dist","build",".vite",".cache"]),ko=[".shogo/"];function Po(e){if(!e)return!0;return e.split("/").some((t)=>vo.has(t))}class se{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??bo,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(() => (ke(),{}));return fo(t)})}start(){if(this.watcher)return;if(this.stopped)throw Error("CloudSyncWatcher: already stopped, build a fresh instance");try{this.watcher=Mt(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=Mt(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(yo).join(So.sep);if(Po(t))return;try{let r=`${this.rootDir}/${t}`;if(wo(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(ko.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})}}ke();var Be=37100,Ke=37900,Nt=1,xo=2,Pe=16,Io=900000,_o=30000,Co=3000,Oo=1000,Ao=60000,jt=8,Ft=300000,Ht=3600000,Mo=8,Wo=120000,Do=900000,Uo=60000,No=500,jo=30000,Lt=25000,Fo=5000,Ho=500,Lo=process.env.SHOGO_PERF_LOG==="1";function Re(e,t,r,o){if(!Lo)return;console.log(`[shogo-perf] ${JSON.stringify({perf:"open",source:"worker",phase:e,projectId:t,openAttemptId:r,atMs:Date.now(),...o})}`)}var Go=(e)=>({command:e,args:[]}),Je=null;function Vt(){if(!Je)Je=Ro(32).toString("hex");return Je}function Gt(e){return Jt("sha256",Vt()).update(`runtime:${e}`).digest("hex")}function Bo(e){return Jt("sha256",Vt()).update(`webhook:${e}`).digest("hex")}function Bt(e){let t=e.indexOf("?");if(t===-1)return{pathname:e,search:""};return{pathname:e.slice(0,t),search:e.slice(t)}}function Kt(e){try{return qt(e).length===0}catch{return!0}}function qe(e,t,r){let o=[];switch(o.push(`Cannot spawn agent-runtime for project ${e}: no workspace directory available.`),o.push(""),t){case"no-auto-pull-config":o.push(" Reason: WorkerRuntimeManager was constructed without an `autoPull` config and no caller-provided `projectDir` was found on disk."),o.push(" This usually means a programmatic embedder forgot to wire up enrichSpawnConfig "+"or autoPull. CLI users should not see this — please file a bug.");break;case"no-projects-dir":o.push(" Reason: auto-pull is configured but `projectsDir` is empty. The worker needs a persistent root directory under which it can store cloned project workspaces.");break;case"auto-pull-disabled":o.push(` Reason: auto-pull was disabled (--no-auto-pull) and the expected pre-pulled workspace at ${r} is missing or empty.`);break}return o.push(""),o.push(" How to fix (pick one):"),o.push(" 1. Re-enable auto-pull (default). Drop the --no-auto-pull flag and restart"),o.push(" the worker. The first inbound request for this project will clone its"),o.push(" workspace from Shogo Cloud into <projectsDir>/<projectId>/."),o.push(""),o.push(" 2. Pre-pull manually with `shogo project pull <projectId>` before starting"),o.push(" the worker. Use this when you want full control over when the clone runs"),o.push(" (slow links, scheduled maintenance windows, etc.)."),o.push(""),o.push(" 3. Point the worker at an existing workspace by setting either:"),o.push(" --projects-dir <path> (per-invocation flag)"),o.push(" SHOGO_PROJECTS_DIR=<path> (env var, persists across restarts)"),o.push(" shogo config set projectsDir <path>"),o.push(" Whichever path you pick must contain a subdirectory named after the "),o.push(` project id (e.g. <path>/${e}/) populated with the project's source.`),o.push(""),o.push(" Docs: https://shogo.ai/docs/self-hosted-worker#workspace-seeding"),o.join(`
11
- `)}class Ve{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??Go,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}=Bt(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 Gt(e)}describeRejection(e,t){let{pathname:r}=Bt(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>=_o).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((l)=>{this.log.warn(`[WorkerRuntimeManager] maxRuntimes eviction of ${i.projectId} failed: ${l?.message??l}`)}),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&&Le(t.projectDir))return t;if(!r)throw Error(qe(e,"no-auto-pull-config",null));if(!r.projectsDir)throw Error(qe(e,"no-projects-dir",null));if(!r.enabled){let s=q(r.projectsDir,e);if(Le(s)&&!Kt(s))return{...t,projectDir:s};throw Error(qe(e,"auto-pull-disabled",s))}if(this.pulledProjects.has(e))return{...t,projectDir:q(r.projectsDir,e)};let o=q(r.projectsDir,e),n=r.logger??this.log,i={cloneProject:r.gitOps?.cloneProject??At,gitIsAvailable:r.gitOps?.gitIsAvailable??Ot,isGitRepo:r.gitOps?.isGitRepo??ve};this.pulledProjects.add(e);try{Wt(o,{recursive:!0});let s=Kt(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 Ge({apiUrl:t.cloudUrl,apiKey:t.apiKey,projectId:e,localDir:o}),m=this.syncModes.get(e)??"files",f=new se({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 Ge({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 Ge({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:t}),s=(await n.listManifest()).filter((l)=>l.path===".shogo"||l.path.startsWith(".shogo/"));if(s.length===0)return;let a=await n.downloadFiles(s);o.log(`[WorkerRuntimeManager] auto-pull: ${e} .shogo/ top-up downloaded ${a.downloaded} files (${a.errors.length} errors)`)}catch(n){o.warn(`[WorkerRuntimeManager] auto-pull: .shogo top-up failed for ${e}: ${n?.message??n}`)}}shouldDeferToCloudSync(e){return!!this.opts.autoPull?.enabled&&this.pulledProjects.has(e)}status(e){let t=this.runtimes.get(e);return t?this.snapshot(t):null}getActiveProjects(){return Array.from(this.runtimes.keys()).filter((e)=>{let t=this.runtimes.get(e);return t&&(t.status==="running"||t.status==="starting"||t.status==="restarting")})}touch(e){let t=this.runtimes.get(e);if(!t)return;t.lastUsedAt=Date.now(),this.armIdleTimer(t)}async stop(e,t="SIGTERM",r="stop"){let o=this.runtimes.get(e);if(!o)return;if(o.status="stopping",o.restartTimer)clearTimeout(o.restartTimer),o.restartTimer=null;if(o.idleTimer)clearTimeout(o.idleTimer),o.idleTimer=null;if(o.graceTimer)clearTimeout(o.graceTimer),o.graceTimer=null;if(o.limitWatchdog)o.limitWatchdog.stop(),o.limitWatchdog=null;if(o.proc){this.killProcessGroup(o,t),this.killViteWatch(o);try{o.proc.kill(t)}catch{}await this.waitForExit(o.proc,5000),this.killProcessGroup(o,"SIGKILL"),this.killViteWatch(o)}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=Wo*Math.pow(2,t);return Math.min(r,Do)}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+Nt;let r=this.buildEnv(e,t.path);if(e.runtimeToken=r.RUNTIME_AUTH_SECRET,r.SHOGO_EXEC_PIDS_FILE)try{To(r.SHOGO_EXEC_PIDS_FILE,{force:!0})}catch{}let o=this.resolveCwd(e),n=this.spawnCommand(t.path),i=this.resolveLimits(),{command:s,args:a}=n,l=!1;if(i){let d=xt({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,l=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 c=process.platform!=="win32",u=T(s,a,{cwd:o,env:r,detached:c,stdio:["ignore","pipe","pipe"]});if(c)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(Re("agent-spawned",e.projectId,p,{pid:e.pid,command:s,args:a,source:t.source}),i)try{this.applyPostSpawnLimits(e,i,l)}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,Re("agent-first-output",e.projectId,p,{elapsedMs:Date.now()-(e.startedAt??Date.now())});for(let w of d.toString().trimEnd().split(`
12
- `))if(w)this.log.log(`${m} ${w}`)}),u.stderr?.on("data",(d)=>{if(e.lastStdoutAt=Date.now(),f)f=!1,Re("agent-first-output",e.projectId,p,{elapsedMs:Date.now()-(e.startedAt??Date.now()),stream:"stderr"});for(let w of d.toString().trimEnd().split(`
13
- `))if(w)this.log.error(`${m} ${w}`)});try{return await this.waitForHealth(e,jo),Re("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=$t(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(It({pid:o,limits:t,logger:this.log}))return}else if(process.platform==="win32"){if(_t({pid:o,limits:t,logger:this.log}))return}let n=this.execPidsFile(e);e.limitWatchdog=new Ne({pid:o,ceilingMB:t.memoryMB,excludePids:()=>Ct(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{ge("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=[]},Uo);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+xo),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:Gt(e.projectId),WEBHOOK_TOKEN:Bo(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=q(Eo(t),"tree-sitter-wasm");if(r.extraEnv)Object.assign(o,r.extraEnv);let n=this.resolveLimits();if(n)Et(o,n.memoryMB),o.SHOGO_EXEC_PIDS_FILE=this.execPidsFile(e),o.SHOGO_EXEC_MEMORY_MB??=String(n.memoryMB);return o}execPidsFile(e){return q(Dt(),"shogo-exec-pids",`${e.projectId.replace(/[^\w.-]/g,"_")}.json`)}killViteWatch(e){let t=e.spawnConfig.projectDir;if(!t)return;try{Ut(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=q(e,r.name);try{let n=Ut(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&&Le(t.projectDir))return t.projectDir;let r=this.opts.runtimeWorkDir??q(Dt(),"shogo-runtime",e.projectId);return Wt(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<=Ft;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((c)=>n-c<=Ht);let s=e.consecutiveFailures>=jt,a=e.restartTimestamps.length>=Mo;if(s||a){e.status="failed",e.breakerTrips+=1;let c=this.breakerCooldownMs(e.breakerTrips),u=s?`${e.consecutiveFailures} consecutive non-clean exits within ${Math.round(Ft/1000)}s`:`${e.restartTimestamps.length} non-clean exits within the last ${Math.round(Ht/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(c/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 l=this.restartBackoffMs(e.restarts);if(e.status="restarting",this.log.warn(`[WorkerRuntimeManager] restarting ${e.projectId} in ${Math.round(l/1000)}s (restart #${e.restarts}, consecutive failures ${e.consecutiveFailures}/${jt})`),e.restartTimer)clearTimeout(e.restartTimer);e.restartTimer=setTimeout(()=>{if(e.restartTimer=null,e.status==="failed"||this.stopped)return;e.startPromise=this.doStart(e).then((c)=>(e.startPromise=null,c)).catch((c)=>(e.startPromise=null,this.log.error(`[WorkerRuntimeManager] restart of ${e.projectId} failed: ${c?.message??c}`),e))},l);try{e.restartTimer.unref?.()}catch{}}restartBackoffMs(e){let t=Math.min(Oo*Math.pow(2,Math.max(0,e-1)),Ao),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??Io;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(Co)});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=Ke-Be,t=Math.min(e,50);for(let r=0;r<t;r++){let o=Be+Math.floor(Math.random()*e);if(o+Pe-1>Ke)continue;let n=!0;for(let a=0;a<Pe;a++)if(this.usedPorts.has(o+a)){n=!1;break}if(!n)continue;let i=await this.isPortListening(o),s=await this.isPortListening(o+Nt);if(i||s)continue;for(let a=0;a<Pe;a++)this.usedPorts.add(o+a);return o}throw Error(`Cannot allocate port in range ${Be}-${Ke} after ${t} attempts`)}releasePort(e){if(!e)return;for(let t=0;t<Pe;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=$o({host:"127.0.0.1",port:e});n.setTimeout(Ho),n.once("connect",()=>o(!0)),n.once("error",()=>o(!1)),n.once("timeout",()=>o(!1))})}async waitForHealth(e,t){let{agentPort:r,proc:o}=e;if(!o)throw Error(`waitForHealth: slot ${e.projectId} has no spawned process`);let n=Date.now(),i=n+t,s=null,a=!1,l=0,c=0,u=0,p=0,m=n;while(Date.now()<i){if(p++,o.exitCode!==null||o.signalCode!=null||o.killed)throw Error(`agent-runtime exited (code=${o.exitCode}, signal=${o.signalCode}) before becoming healthy on port ${r}`);c++;let f=new AbortController,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, ${c} http)`);return}s=`HTTP /health returned ${g.status}`}catch(g){clearTimeout(d);let v=g?.name??"Error",Q=g?.code??g?.cause?.code;s=`HTTP /health failed: ${v}${Q?`(${Q})`:""}: ${g?.message??g}`}if(u++,a=await this.tcpProbe(r),a){l=Date.now();let g=Date.now()-e.lastStdoutAt;if(g>=Lt)throw Error(`agent-runtime wedged on port ${r}: TCP listening but stdout silent for ${g}ms (> ${Lt}ms window); ${c} /health attempts, last error: ${s??"n/a"}`)}let w=Date.now();if(w-m>=Fo){let g=w-n,v=w-e.lastStdoutAt,Q=l>0?w-l:null;this.log.log(`[WorkerRuntimeManager] still waiting for /health on ${e.projectId} port ${r} (${(g/1000).toFixed(1)}s elapsed, tcpListening=${a}${Q!=null?`(${Q}ms ago)`:""}, lastStdout=${v}ms ago, ${c} http, ${u} tcp, lastError=${s??"n/a"})`),m=w}await new Promise((g)=>setTimeout(g,No))}if(o.exitCode!==null||o.signalCode!=null||o.killed)throw Error(`agent-runtime exited (code=${o.exitCode}, signal=${o.signalCode}) before becoming healthy on port ${r}`);throw Error(`Timeout waiting for agent-runtime /health on port ${r} after ${p} iter (httpAttempts=${c}, tcpAttempts=${u}, tcpListening=${a}, lastError=${s??"n/a"})`)}async waitForExit(e,t){if(e.exitCode!==null||e.signalCode!=null||e.killed)return;await new Promise((r)=>{let o=setTimeout(()=>{try{e.kill("SIGKILL")}catch{}r()},t);e.once("exit",()=>{clearTimeout(o),r()})})}snapshot(e){return{projectId:e.projectId,status:e.status,agentPort:e.agentPort||void 0,apiServerPort:e.apiServerPort||void 0,pid:e.proc?.pid,startedAt:e.startedAt||void 0,lastUsedAt:e.lastUsedAt,restarts:e.restarts,lastError:e.lastError}}}import{hostname as Ye,platform as Xe,arch as ze}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 Qt 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 ae=60,Te=300,Qe=3,Ko=Qe,Jo=1800000,qo=25000,Yt=1000,Xt=60000,zt=3;class Ze{opts;log;pollTimer=null;ws=null;wsIdleTimer=null;heartbeatTimer=null;stopped=!1;currentPollInterval=ae;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=ae,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 Qt;return new WebSocket(e,t)}getReconnectDelayMs(){let e=Math.min(Yt*Math.pow(2,this.wsReconnectAttempt),Xt),t=e*0.2*Math.random();return e+t}async collectMetadata(){let e={hostname:Ye(),os:Xe(),arch:ze(),uptime:process.uptime(),protocolVersion:zt,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=Ye(),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:Xe(),arch:ze(),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||ae;if(this.consecutiveAuthFailures>=Qe){if(this.consecutiveAuthSuccesses++,this.consecutiveAuthSuccesses<Ko){this.currentPollInterval=Te,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>=Qe){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!==Te)this.log.warn(`[WorkerTunnel] ${this.consecutiveAuthFailures} consecutive auth failures — `+`backing off to ${Te}s. Re-authenticate with \`shogo login\`.`);this.currentPollInterval=Te}else this.currentPollInterval=ae}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:l,value:c}=await s.read();if(l)break;if(this.ws?.readyState!==WebSocket.OPEN)break;this.sendFrame({type:"stream-chunk",requestId:e.requestId,data:a.decode(c,{stream:!0})})}if(this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-end",requestId:e.requestId})}catch(l){if(l?.name!=="AbortError"&&this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-error",requestId:e.requestId,error:l?.message??String(l)})}}else{let s=await i.text(),a={};i.headers.forEach((l,c)=>{a[c]=l}),this.sendFrame({type:"response",requestId:e.requestId,status:i.status,headers:a,body:s})}}catch(r){if(r?.name==="AbortError")return;if(this.ws?.readyState!==WebSocket.OPEN)return;let o=e.stream?{type:"stream-error",requestId:e.requestId,error:r?.message??String(r)}:{type:"response",requestId:e.requestId,status:502,body:JSON.stringify({error:r?.message??String(r)})};this.sendFrame(o)}finally{this.activeAbortControllers.delete(e.requestId)}}async resolveLocalUrl(e,t){return this.opts.resolver.resolveLocalUrl(e,t)}sendFrame(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;try{this.ws.send(JSON.stringify(e))}catch(t){this.log.warn(`[WorkerTunnel] Frame send failed: ${t?.message??t}`)}}resetWsIdleTimer(){if(this.wsIdleTimer)clearTimeout(this.wsIdleTimer);this.wsIdleTimer=setTimeout(()=>{if(this.ws&&this.ws.readyState===WebSocket.OPEN){this.log.log("[WorkerTunnel] WebSocket idle timeout — closing, returning to polling");try{this.ws.close(1000,"Idle timeout")}catch{}}},Jo)}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{}},qo)}connectWs(){if(this.stopped||this.ws)return;let e=this.buildWsUrl(),t=Ye(),r=Xe(),o=ze(),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 l;try{let c=typeof a.data==="string"?a.data:a.data.toString();l=JSON.parse(c)}catch{return}if(l.type==="ping"){this.sendFrame({type:"pong"}),this.resetWsIdleTimer();return}if(l.type==="cancel"){let c=this.activeAbortControllers.get(l.requestId);if(c)c.abort();return}if(l.type==="request"){this.handleRequest(l).catch((c)=>{this.log.error(`[WorkerTunnel] Error handling request: ${c?.message??c}`)});return}},s.onclose=(a)=>{if(this.log.log(`[WorkerTunnel] WebSocket closed: code=${a.code} reason=${a.reason||"none"}`),this.cleanupWs(),this.stopped)return;if(a.code===1000||a.code===4000)this.scheduleNextPoll(this.currentPollInterval);else{this.wsReconnectAttempt++;let l=this.getReconnectDelayMs();this.log.log(`[WorkerTunnel] Reconnecting in ${Math.round(l/1000)}s (attempt ${this.wsReconnectAttempt})`),this.scheduleNextPoll(Math.ceil(l/1000))}},s.onerror=(a)=>{this.log.error("[WorkerTunnel] WebSocket error:",a?.message??"unknown")}}cleanupWs(){if(this.heartbeatTimer)clearInterval(this.heartbeatTimer),this.heartbeatTimer=null;if(this.wsIdleTimer)clearTimeout(this.wsIdleTimer),this.wsIdleTimer=null;for(let[,e]of this.activeAbortControllers)try{e.abort()}catch{}this.activeAbortControllers.clear(),this.ws=null}_testing(){let e=this;return{sendHeartbeat:()=>e.sendHeartbeat(),heartbeatLoop:()=>e.heartbeatLoop(),connectWs:()=>e.connectWs(),cleanupWs:()=>e.cleanupWs(),handleRequest:(t)=>e.handleRequest(t),installFakeWs:(t)=>{e.ws=t},getCloudUrl:()=>e.getCloudUrl(),getWsBaseUrl:()=>e.getWsBaseUrl(),buildWsUrl:()=>e.buildWsUrl(),getReconnectDelayMs:()=>e.getReconnectDelayMs(),supportsWebSocketConstructorHeaders:(t)=>e.supportsWebSocketConstructorHeaders(t),createTunnelWebSocket:(t,r,o)=>e.createTunnelWebSocket(t,r,o),DEFAULT_POLL_INTERVAL_S:ae,BACKOFF_BASE_MS:Yt,BACKOFF_MAX_MS:Xt,TUNNEL_PROTOCOL_VERSION:zt,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 Zt(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=yt(e.proxy);if(!e.foreground)return Yo({cfg:t,proxy:r,flags:e});let o=G({flag:e.runtimeBin});if(!o)console.error(h.red(J({flag:e.runtimeBin}))),process.exit(1);if(De(process.env,r),e.debug){if(!await kt(vt({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(`
14
- 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"}},l=new Ve({runtimeBin:e.runtimeBin,defaultSpawnConfig:a,autoPull:{enabled:n,projectsDir:t.projectsDir,watch:!0,useGit:i}});if(!l.resolveBinary())console.error(h.red(J({flag:e.runtimeBin}))),process.exit(1);let c=new Ze({apiKey:t.apiKey,cloudUrl:t.cloudUrl,name:t.name,kind:"cli-worker",resolver:l,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(`
15
- Received ${m} — shutting down...`));try{c.stop()}catch{}try{await l.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")),c.start(),console.log(h.green("✓ Worker running. Ctrl-C to stop.")),await new Promise(()=>{})}function Yo({cfg:e,proxy:t,flags:r}){let{entry:o,runner:n}=zo(),i=De({...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=Xo(r),{pid:a}=ht({entry:o,runner:n,env:{...i,SHOGO_DETACHED_ARGS:s.join(" ")},cwd:e.workerDir,detach:!0,inheritStdio:!1});console.log(h.bold(`
16
- 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 Xo(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 zo(){let e=process.execPath,t=/\bbun(?:-[^/\\]*)?$/.test(e)||typeof globalThis.Bun<"u",r=process.argv[1];if(r&&Vo(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 er from"picocolors";async function tr(){let{killedPid:e}=ft("SIGTERM");if(e===null){console.log(er.dim("No worker running."));return}console.log(er.green(`✓ Worker stopped (pid=${e}).`))}import N from"picocolors";async function rr(){let e=fe();if(!e){console.log(N.yellow("● stopped")+N.dim(" (no pid file)"));return}if(!we(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 Zo}from"node:fs";import or from"picocolors";async function nr(e){let t=e.err?de:pe;if(!Qo(t)){console.log(or.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(Zo(t).size===0)console.log(or.dim("(log file is empty)"))}}import x from"picocolors";W();import{hostname as en,platform as tn,arch as rn}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 ir(e){let t=e.cloudUrl.replace(/\/$/,""),r=e.client??"cli",o=e.deviceName??en(),n=e.devicePlatform??`${tn()}-${rn()}`,i=e.appVersion??sn(),s=e.log??((g)=>console.log(g)),a=e.fetchImpl??fetch,l=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(!l.ok){let g=await l.text().catch(()=>"");throw new C(`Cloud rejected /api/cli/login/start (HTTP ${l.status}): ${g||"no body"}`,"transport")}let c=await l.json().catch(()=>({}));if(!c?.ok||!c.state||!c.authUrl)throw new C(`Cloud returned a malformed start response: ${c?.error??JSON.stringify(c)}`,"transport");let u=on(e.pollIntervalMs??c.pollIntervalMs),p=e.timeoutMs??c.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(c.userCode)),s(""),s(" Open this URL in your browser to approve:"),s(" "+B.cyan(c.authUrl)),s(""),e.openBrowser!==!1)if(typeof e.openBrowser==="function")try{await e.openBrowser(c.authUrl)}catch{}else an(c.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(c.state)}`,{method:"GET",headers:{accept:"application/json","user-agent":V()},signal:AbortSignal.timeout(1e4)}).catch((v)=>(s(B.dim(` (poll error: ${v?.message??v} — retrying)`)),null));if(g&&g.ok){let v=await g.json().catch(()=>({}));if(v?.status==="approved"&&v.key)return{key:v.key,email:v.email??null,workspace:v.workspace??null,deviceId:v.deviceId??e.deviceId,workspaces:v.workspaces??null};if(v?.status==="denied")throw new C("Sign-in was denied in the browser.","denied");if(v?.status==="expired")throw new C("Sign-in request expired before it was approved.","expired")}await nn(u,w.signal)}}finally{if(f)process.removeListener("SIGINT",d),process.removeListener("SIGTERM",d)}}function on(e){if(!Number.isFinite(e)||e<=0)return 2000;return Math.min(Math.max(e,1000),1e4)}function nn(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 sn(){return V()}function an(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 ln,writeFileSync as cn,existsSync as un,chmodSync as pn}from"node:fs";import{randomUUID as dn}from"node:crypto";function sr(){if(un(Z)){let t=ln(Z,"utf-8").trim();if(t)return t}H();let e=dn();return cn(Z,e,{mode:384}),pn(Z,384),e}var mn="https://studio.shogo.ai";async function ar(e){let t=L(),r=(e.cloudUrl||t.cloudUrl||mn).replace(/\/$/,""),o=e.apiKey||process.env.SHOGO_API_KEY;if(o){await gn({key:o,cloudUrl:r,cfg:t,name:e.name});return}let n=sr(),i;try{i=await ir({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(oe(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 gn({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(oe(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 le from"picocolors";async function lr(){let e=L();if(Object.keys(e).length===0){console.log(le.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(le.dim(`file: ${F}`)),console.log(JSON.stringify(t,null,2))}async function cr(e,t){let r=["apiKey","cloudUrl","name","workerDir","port"];if(!r.includes(e))console.error(le.red(`Unknown key: ${e}`)),console.error(le.dim(`Allowed: ${r.join(", ")}`)),process.exit(1);let o=L();o[e]=e==="port"?parseInt(t,10):t,oe(o),console.log(le.green(`✓ ${e} set`))}import b from"picocolors";W();import{createHash as hn}from"node:crypto";import{createWriteStream as fn,existsSync as ce,mkdirSync as et,readFileSync as dr,renameSync as ur,rmSync as tt,writeFileSync as wn}from"node:fs";import{tmpdir as yn}from"node:os";import{dirname as Sn,join as $e}from"node:path";import{Readable as bn}from"node:stream";import{pipeline as vn}from"node:stream/promises";var kn="https://github.com/shogo-labs/shogo-ai/releases/download";function mr(){let e=process.platform,t=process.arch,r;if(e==="darwin")r="darwin";else if(e==="linux")r="linux";else if(e==="win32")r="windows";else throw Error(`Unsupported platform: ${e}`);let o;if(t==="arm64")o="arm64";else if(t==="x64")o="x64";else throw Error(`Unsupported arch: ${t} (need arm64 or x64)`);return`${r}-${o}`}function Ee(){if(!ce(re))return null;try{let e=dr(re,"utf-8");return JSON.parse(e)}catch{return null}}async function Pn(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 pr(m.tag_name)}let i=`https://api.github.com/repos/${o}/${n}/releases?per_page=30`,s=await fetch(i,{headers:{Accept:"application/vnd.github+json"}});if(!s.ok)throw Error(`GitHub API ${s.status} for ${i}`);let a=await s.json(),l=e==="beta"?"-beta":"-nightly",c=a.find((u)=>u.prerelease&&/^v\d+\.\d+\.\d+-/.test(u.tag_name)&&u.tag_name.includes(l));if(!c)throw Error(`No ${e} runtime release found`);return pr(c.tag_name)}function pr(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 Rn(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 Tn(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 vn(bn.fromWeb(r.body),fn(t))}async function $n(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 En(e){let t=dr(e);return hn("sha256").update(t).digest("hex")}async function xn(e,t){await new Promise((r,o)=>{let n=T("tar",["-xzf",e,"-C",t],{stdio:["ignore","pipe","pipe"]}),i="";n.stderr.on("data",(s)=>{i+=s.toString()}),n.on("error",o),n.on("exit",(s)=>{if(s===0)r();else o(Error(`tar exited ${s}: ${i.trim()}`))})})}function In(e,t){st();let r=Sn(t);if(!ce(r))et(r,{recursive:!0,mode:448});let o=`${t}.next`;if(ce(o))tt(o,{force:!0});ur(e,o);try{if(process.platform!=="win32"){let{chmodSync:n}=Ce("node:fs");n(o,493)}}catch{}if(ce(t))tt(t,{force:!0});ur(o,t)}async function gr(e={}){let t=e.logger??console,r=e.channel??"stable",o=e.baseUrl??process.env.SHOGO_RUNTIME_RELEASES_URL??kn,n=e.target??mr(),i=e.version;if(!i)t.log(`[runtime install] Resolving latest ${r} version...`),i=await Pn(r,o),t.log(`[runtime install] Latest ${r} = ${i}`);let s=Ee();if(s&&s.version===i&&s.target===n&&!e.force)return t.log(`[runtime install] ${i} (${n}) already installed at ${A} — pass --force to reinstall`),{version:i,target:n,binPath:A,source:s.source,sha256:s.sha256,channel:r};let a=Rn(i,n,o);t.log(`[runtime install] Downloading ${a.tarball}`);let l=$e(yn(),`shogo-runtime-install-${process.pid}-${Date.now()}`);et(l,{recursive:!0});try{let c=$e(l,a.assetName);await Tn(a.tarball,c),t.log("[runtime install] Verifying SHA-256...");let u=await $n(a.sha256),p=En(c);if(p!==u)throw Error(`SHA-256 mismatch for ${a.assetName}
9
+ `));return!t}import{accessSync as Qr,constants as Zr,existsSync as eo}from"node:fs";import{delimiter as to,join as ro}from"node:path";function G(e={}){let t=e.env??process.env,r=oo(e);for(let i of r)if($t(i.path))return i;let o=e.systemBinName??Tt(),n=no(o,t.PATH);if(n)return{path:n,source:"path"};return null}function oo(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 Tt(){return process.platform==="win32"?"shogo-agent-runtime.exe":"shogo-agent-runtime"}function $t(e){if(!eo(e))return!1;try{if(process.platform==="win32")return!0;return Qr(e,Zr.X_OK),!0}catch{return!1}}function no(e,t){if(!t)return null;for(let r of t.split(to)){if(!r)continue;let o=ro(r,e);if($t(o))return o}return null}function J(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(` ${Tt()} 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 Vt,randomBytes as Eo}from"node:crypto";import{existsSync as Be,mkdirSync as Ut,readdirSync as Yt,rmSync as xo}from"node:fs";import{createConnection as Io}from"node:net";import{tmpdir as Nt}from"node:os";import{dirname as _o,join as q}from"node:path";W();import{existsSync as io,mkdirSync as so,readFileSync as ao,writeFileSync as ye}from"node:fs";import{join as X}from"node:path";var Et=1e5;function xt(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 It(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 lo(){if(process.platform!=="linux")return!1;try{return ge("systemd-run",["--version"],{stdio:"ignore",timeout:3000}).status===0}catch{return!1}}function _t(e){let{command:t,args:r,limits:o,scopeName:n,logger:i}=e;if(process.platform!=="linux"||!lo())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 Ot(e){let{pid:t,limits:r,logger:o}=e;if(process.platform!=="linux")return!1;let n="/sys/fs/cgroup";if(!io(X(n,"cgroup.controllers")))return!1;let i=X(n,"shogo",`rt-${t}`);try{if(so(i,{recursive:!0}),ye(X(i,"memory.max"),String(r.memoryMB*1024*1024)),r.cpuPercent>0){let s=Math.floor(r.cpuPercent/100*Et);ye(X(i,"cpu.max"),`${s} ${Et}`)}try{ye(X(i,"pids.max"),String(r.tasksMax))}catch{}return ye(X(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 Ct(e){let{pid:t,limits:r,logger:o}=e;if(process.platform!=="win32")return!1;let n=process.env.SHOGO_WINJOB_HELPER||"@shogo/winjob";try{let i=Oe(n);if(typeof i.limitProcess!=="function")return!1;let s=i.limitProcess(t,{memoryBytes:r.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 At(e){try{let t=JSON.parse(ao(e,"utf-8"));if(!Array.isArray(t.pids))return[];return t.pids.filter((r)=>Number.isInteger(r)&&r>0)}catch{return[]}}var co=20000,uo=`$ErrorActionPreference='SilentlyContinue';foreach($p in Get-CimInstance Win32_Process -Property ProcessId,ParentProcessId,WorkingSetSize){"$($p.ProcessId) $($p.ParentProcessId) $([math]::Floor($p.WorkingSetSize/1024))"}`;function po(e=process.platform){let[t,r]=e==="win32"?["powershell.exe",["-NoProfile","-NonInteractive","-Command",uo]]:["ps",["-A","-o","pid=,ppid=,pgid=,rss="]];return new Promise((o)=>{he(t,r,{encoding:"utf-8",timeout:co,maxBuffer:8388608},(n,i)=>{if(n||!i){o(null);return}let s=[];for(let a of String(i).split(/\r?\n/)){let l=a.trim().split(/\s+/).map((c)=>parseInt(c,10));if(l.some((c)=>!Number.isFinite(c)))continue;if(e==="win32"&&l.length===3)s.push({pid:l[0],ppid:l[1],pgid:0,kb:l[2]});else if(e!=="win32"&&l.length===4)s.push({pid:l[0],ppid:l[1],pgid:l[2],kb:l[3]})}o(s.length?s:null)})})}function mo(e,t,r=process.platform,o=[]){if(r!=="win32"){let u=e.filter((p)=>p.pgid===t);return u.length?u.reduce((p,g)=>p+g.kb,0):null}if(!e.some((u)=>u.pid===t))return null;let n=new Set(o.filter((u)=>u!==t)),i=new Map;for(let u of e){if(u.pid===u.ppid)continue;let p=i.get(u.ppid);if(p)p.push(u);else i.set(u.ppid,[u])}let s=new Map(e.map((u)=>[u.pid,u])),a=new Set,l=[t],c=0;while(l.length){let u=l.pop();if(a.has(u))continue;a.add(u),c+=s.get(u)?.kb??0;for(let p of i.get(u)??[])if(!n.has(p.pid))l.push(p.pid)}return c}var Ue=new Map;function go(e,t,r){let o=`${t}:${r}`,n=Ue.get(o);if(!n){let i={key:o,watchdogs:new Set,sampling:!1,timer:setInterval(()=>{ho(i,t)},r)};try{i.timer.unref?.()}catch{}Ue.set(o,i),n=i}return n.watchdogs.add(e),n}async function ho(e,t){if(e.sampling)return;e.sampling=!0;try{let r=await po(t);for(let o of[...e.watchdogs])o.observe(r)}finally{e.sampling=!1}}class Ne{sampler=null;consecutive=0;fired=!1;pid;ceilingMB;platform;intervalMs;strikes;excludePids;logger;onBreach;constructor(e){this.pid=e.pid,this.ceilingMB=e.ceilingMB,this.platform=e.platform??process.platform,this.intervalMs=e.intervalMs??parseInt(process.env.RUNTIME_RSS_WATCHDOG_MS||"15000",10),this.strikes=e.strikes??parseInt(process.env.RUNTIME_RSS_WATCHDOG_STRIKES||"2",10),this.excludePids=e.excludePids,this.logger=e.logger,this.onBreach=e.onBreach}start(){if(this.sampler)return;this.sampler=go(this,this.platform,Math.max(2000,this.intervalMs))}stop(){if(this.sampler?.watchdogs.delete(this),this.sampler?.watchdogs.size===0)clearInterval(this.sampler.timer),Ue.delete(this.sampler.key);this.sampler=null}observe(e){if(this.fired)return;let t=e?mo(e,this.pid,this.platform,this.excludePids?.()??[]):null;if(t==null){this.consecutive=0;return}let r=Math.floor(t/1024);if(r<=this.ceilingMB){this.consecutive=0;return}if(this.consecutive+=1,this.logger?.warn(`[resource-limits] pid ${this.pid} RSS ${r}MB over ceiling ${this.ceilingMB}MB (strike ${this.consecutive}/${this.strikes})`),this.consecutive>=this.strikes)this.fired=!0,this.stop(),this.onBreach(r)}}import{CloudFileTransport as Ke}from"@shogo-ai/sdk/cloud-file-transport";import{killViteWatchFromPidfile as jt}from"@shogo-ai/sdk/vite-watch";import{watch as Dt,statSync as bo}from"node:fs";import{relative as qi,sep as vo,posix as ko}from"node:path";var Po=1500,Ro=new Set(["node_modules",".git","dist","build",".vite",".cache"]),To=[".shogo/"];function $o(e){if(!e)return!0;return e.split("/").some((t)=>Ro.has(t))}class se{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??Po,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(() => (ke(),{}));return So(t)})}start(){if(this.watcher)return;if(this.stopped)throw Error("CloudSyncWatcher: already stopped, build a fresh instance");try{this.watcher=Dt(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=Dt(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(vo).join(ko.sep);if($o(t))return;try{let r=`${this.rootDir}/${t}`;if(bo(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(To.some((s)=>i===s.slice(0,-1)||i.startsWith(s)))t.push(i);else r.push(i);if(t.length>0){let i=await this.transport.uploadFiles(t);if(i.errors.length>0)for(let s of i.errors)this.logger.warn(`[CloudSyncWatcher] upload ${s.path}: ${s.message}`)}let o=!1,n;if(r.length>0){let i=`auto: ${new Date().toISOString()}`;try{let s=await this.commitAndPush({apiUrl:this.git.apiUrl,apiKey:this.git.apiKey,projectId:this.git.projectId,localDir:this.rootDir,message:i,branch:this.git.branch,logger:this.logger});o=s.committed,n=s.commitSha}catch(s){throw s}}this.onFlush?.({uploaded:e,errors:0,committed:o,commitSha:n})}}ke();var Je=37100,qe=37900,Ft=1,Oo=2,Pe=16,Co=900000,Ao=30000,Mo=3000,Wo=1000,Do=60000,Ht=8,Lt=300000,Gt=3600000,Uo=8,No=120000,jo=900000,Fo=60000,Ho=500,Lo=30000,Bt=25000,Go=5000,Bo=500,Ko=process.env.SHOGO_PERF_LOG==="1";function Re(e,t,r,o){if(!Ko)return;console.log(`[shogo-perf] ${JSON.stringify({perf:"open",source:"worker",phase:e,projectId:t,openAttemptId:r,atMs:Date.now(),...o})}`)}var Jo=(e)=>({command:e,args:[]}),Ve=null;function Xt(){if(!Ve)Ve=Eo(32).toString("hex");return Ve}function Kt(e){return Vt("sha256",Xt()).update(`runtime:${e}`).digest("hex")}function qo(e){return Vt("sha256",Xt()).update(`webhook:${e}`).digest("hex")}function Jt(e){let t=e.indexOf("?");if(t===-1)return{pathname:e,search:""};return{pathname:e.slice(0,t),search:e.slice(t)}}function qt(e){try{return Yt(e).length===0}catch{return!0}}function Ye(e,t,r){let o=[];switch(o.push(`Cannot spawn agent-runtime for project ${e}: no workspace directory available.`),o.push(""),t){case"no-auto-pull-config":o.push(" Reason: WorkerRuntimeManager was constructed without an `autoPull` config and no caller-provided `projectDir` was found on disk."),o.push(" This usually means a programmatic embedder forgot to wire up enrichSpawnConfig "+"or autoPull. CLI users should not see this — please file a bug.");break;case"no-projects-dir":o.push(" Reason: auto-pull is configured but `projectsDir` is empty. The worker needs a persistent root directory under which it can store cloned project workspaces.");break;case"auto-pull-disabled":o.push(` Reason: auto-pull was disabled (--no-auto-pull) and the expected pre-pulled workspace at ${r} is missing or empty.`);break}return o.push(""),o.push(" How to fix (pick one):"),o.push(" 1. Re-enable auto-pull (default). Drop the --no-auto-pull flag and restart"),o.push(" the worker. The first inbound request for this project will clone its"),o.push(" workspace from Shogo Cloud into <projectsDir>/<projectId>/."),o.push(""),o.push(" 2. Pre-pull manually with `shogo project pull <projectId>` before starting"),o.push(" the worker. Use this when you want full control over when the clone runs"),o.push(" (slow links, scheduled maintenance windows, etc.)."),o.push(""),o.push(" 3. Point the worker at an existing workspace by setting either:"),o.push(" --projects-dir <path> (per-invocation flag)"),o.push(" SHOGO_PROJECTS_DIR=<path> (env var, persists across restarts)"),o.push(" shogo config set projectsDir <path>"),o.push(" Whichever path you pick must contain a subdirectory named after the "),o.push(` project id (e.g. <path>/${e}/) populated with the project's source.`),o.push(""),o.push(" Docs: https://shogo.ai/docs/self-hosted-worker#workspace-seeding"),o.join(`
11
+ `)}class Xe{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??Jo,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}=Jt(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 Kt(e)}describeRejection(e,t){let{pathname:r}=Jt(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>=Ao).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((l)=>{this.log.warn(`[WorkerRuntimeManager] maxRuntimes eviction of ${i.projectId} failed: ${l?.message??l}`)}),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&&Be(t.projectDir))return t;if(!r)throw Error(Ye(e,"no-auto-pull-config",null));if(!r.projectsDir)throw Error(Ye(e,"no-projects-dir",null));if(!r.enabled){let s=q(r.projectsDir,e);if(Be(s)&&!qt(s))return{...t,projectDir:s};throw Error(Ye(e,"auto-pull-disabled",s))}if(this.pulledProjects.has(e))return{...t,projectDir:q(r.projectsDir,e)};let o=q(r.projectsDir,e),n=r.logger??this.log,i={cloneProject:r.gitOps?.cloneProject??Wt,gitIsAvailable:r.gitOps?.gitIsAvailable??Mt,isGitRepo:r.gitOps?.isGitRepo??ve};this.pulledProjects.add(e);try{Ut(o,{recursive:!0});let s=qt(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 Ke({apiUrl:t.cloudUrl,apiKey:t.apiKey,projectId:e,localDir:o}),g=this.syncModes.get(e)??"files",f=new se({rootDir:o,transport:p,logger:n,mode:g,git:g==="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 Ke({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 Ke({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:t}),s=(await n.listManifest()).filter((l)=>l.path===".shogo"||l.path.startsWith(".shogo/"));if(s.length===0)return;let a=await n.downloadFiles(s);o.log(`[WorkerRuntimeManager] auto-pull: ${e} .shogo/ top-up downloaded ${a.downloaded} files (${a.errors.length} errors)`)}catch(n){o.warn(`[WorkerRuntimeManager] auto-pull: .shogo top-up failed for ${e}: ${n?.message??n}`)}}shouldDeferToCloudSync(e){return!!this.opts.autoPull?.enabled&&this.pulledProjects.has(e)}status(e){let t=this.runtimes.get(e);return t?this.snapshot(t):null}getActiveProjects(){return Array.from(this.runtimes.keys()).filter((e)=>{let t=this.runtimes.get(e);return t&&(t.status==="running"||t.status==="starting"||t.status==="restarting")})}touch(e){let t=this.runtimes.get(e);if(!t)return;t.lastUsedAt=Date.now(),this.armIdleTimer(t)}async stop(e,t="SIGTERM",r="stop"){let o=this.runtimes.get(e);if(!o)return;if(o.status="stopping",o.restartTimer)clearTimeout(o.restartTimer),o.restartTimer=null;if(o.idleTimer)clearTimeout(o.idleTimer),o.idleTimer=null;if(o.graceTimer)clearTimeout(o.graceTimer),o.graceTimer=null;if(o.limitWatchdog)o.limitWatchdog.stop(),o.limitWatchdog=null;if(o.proc){this.killProcessGroup(o,t),this.killViteWatch(o);try{o.proc.kill(t)}catch{}await this.waitForExit(o.proc,5000),this.killProcessGroup(o,"SIGKILL"),this.killViteWatch(o)}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=No*Math.pow(2,t);return Math.min(r,jo)}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+Ft;let r=this.buildEnv(e,t.path);if(e.runtimeToken=r.RUNTIME_AUTH_SECRET,r.SHOGO_EXEC_PIDS_FILE)try{xo(r.SHOGO_EXEC_PIDS_FILE,{force:!0})}catch{}let o=this.resolveCwd(e),n=this.spawnCommand(t.path),i=this.resolveLimits(),{command:s,args:a}=n,l=!1;if(i){let d=_t({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,l=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 c=process.platform!=="win32",u=T(s,a,{cwd:o,env:r,detached:c,stdio:["ignore","pipe","pipe"]});if(c)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(Re("agent-spawned",e.projectId,p,{pid:e.pid,command:s,args:a,source:t.source}),i)try{this.applyPostSpawnLimits(e,i,l)}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 g=`[runtime:${e.projectId.slice(0,8)}]`,f=!0;u.stdout?.on("data",(d)=>{if(e.lastStdoutAt=Date.now(),f)f=!1,Re("agent-first-output",e.projectId,p,{elapsedMs:Date.now()-(e.startedAt??Date.now())});for(let w of d.toString().trimEnd().split(`
12
+ `))if(w)this.log.log(`${g} ${w}`)}),u.stderr?.on("data",(d)=>{if(e.lastStdoutAt=Date.now(),f)f=!1,Re("agent-first-output",e.projectId,p,{elapsedMs:Date.now()-(e.startedAt??Date.now()),stream:"stderr"});for(let w of d.toString().trimEnd().split(`
13
+ `))if(w)this.log.error(`${g} ${w}`)});try{return await this.waitForHealth(e,Lo),Re("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=xt(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(Ot({pid:o,limits:t,logger:this.log}))return}else if(process.platform==="win32"){if(Ct({pid:o,limits:t,logger:this.log}))return}let n=this.execPidsFile(e);e.limitWatchdog=new Ne({pid:o,ceilingMB:t.memoryMB,excludePids:()=>At(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{ge("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=[]},Fo);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+Oo),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:Kt(e.projectId),WEBHOOK_TOKEN:qo(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=q(_o(t),"tree-sitter-wasm");if(r.extraEnv)Object.assign(o,r.extraEnv);let n=this.resolveLimits();if(n)It(o,n.memoryMB),o.SHOGO_EXEC_PIDS_FILE=this.execPidsFile(e),o.SHOGO_EXEC_MEMORY_MB??=String(n.memoryMB);return o}execPidsFile(e){return q(Nt(),"shogo-exec-pids",`${e.projectId.replace(/[^\w.-]/g,"_")}.json`)}killViteWatch(e){let t=e.spawnConfig.projectDir;if(!t)return;try{jt(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=Yt(e,{withFileTypes:!0})}catch{return}for(let r of t){if(!r.isDirectory()||this.runtimes.has(r.name))continue;let o=q(e,r.name);try{let n=jt(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&&Be(t.projectDir))return t.projectDir;let r=this.opts.runtimeWorkDir??q(Nt(),"shogo-runtime",e.projectId);return Ut(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<=Lt;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((c)=>n-c<=Gt);let s=e.consecutiveFailures>=Ht,a=e.restartTimestamps.length>=Uo;if(s||a){e.status="failed",e.breakerTrips+=1;let c=this.breakerCooldownMs(e.breakerTrips),u=s?`${e.consecutiveFailures} consecutive non-clean exits within ${Math.round(Lt/1000)}s`:`${e.restartTimestamps.length} non-clean exits within the last ${Math.round(Gt/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(c/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 l=this.restartBackoffMs(e.restarts);if(e.status="restarting",this.log.warn(`[WorkerRuntimeManager] restarting ${e.projectId} in ${Math.round(l/1000)}s (restart #${e.restarts}, consecutive failures ${e.consecutiveFailures}/${Ht})`),e.restartTimer)clearTimeout(e.restartTimer);e.restartTimer=setTimeout(()=>{if(e.restartTimer=null,e.status==="failed"||this.stopped)return;e.startPromise=this.doStart(e).then((c)=>(e.startPromise=null,c)).catch((c)=>(e.startPromise=null,this.log.error(`[WorkerRuntimeManager] restart of ${e.projectId} failed: ${c?.message??c}`),e))},l);try{e.restartTimer.unref?.()}catch{}}restartBackoffMs(e){let t=Math.min(Wo*Math.pow(2,Math.max(0,e-1)),Do),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??Co;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(Mo)});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=qe-Je,t=Math.min(e,50);for(let r=0;r<t;r++){let o=Je+Math.floor(Math.random()*e);if(o+Pe-1>qe)continue;let n=!0;for(let a=0;a<Pe;a++)if(this.usedPorts.has(o+a)){n=!1;break}if(!n)continue;let i=await this.isPortListening(o),s=await this.isPortListening(o+Ft);if(i||s)continue;for(let a=0;a<Pe;a++)this.usedPorts.add(o+a);return o}throw Error(`Cannot allocate port in range ${Je}-${qe} after ${t} attempts`)}releasePort(e){if(!e)return;for(let t=0;t<Pe;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=Io({host:"127.0.0.1",port:e});n.setTimeout(Bo),n.once("connect",()=>o(!0)),n.once("error",()=>o(!1)),n.once("timeout",()=>o(!1))})}async waitForHealth(e,t){let{agentPort:r,proc:o}=e;if(!o)throw Error(`waitForHealth: slot ${e.projectId} has no spawned process`);let n=Date.now(),i=n+t,s=null,a=!1,l=0,c=0,u=0,p=0,g=n;while(Date.now()<i){if(p++,o.exitCode!==null||o.signalCode!=null||o.killed)throw Error(`agent-runtime exited (code=${o.exitCode}, signal=${o.signalCode}) before becoming healthy on port ${r}`);c++;let f=new AbortController,d=setTimeout(()=>f.abort(),1500);try{let m=await fetch(`http://127.0.0.1:${r}/health`,{method:"GET",signal:f.signal});if(clearTimeout(d),m.ok){this.log.log(`[WorkerRuntimeManager] /health ready for ${e.projectId} on port ${r} (HTTP ${m.status} after ${Date.now()-n}ms, ${p} iter, ${c} http)`);return}s=`HTTP /health returned ${m.status}`}catch(m){clearTimeout(d);let v=m?.name??"Error",Q=m?.code??m?.cause?.code;s=`HTTP /health failed: ${v}${Q?`(${Q})`:""}: ${m?.message??m}`}if(u++,a=await this.tcpProbe(r),a){l=Date.now();let m=Date.now()-e.lastStdoutAt;if(m>=Bt)throw Error(`agent-runtime wedged on port ${r}: TCP listening but stdout silent for ${m}ms (> ${Bt}ms window); ${c} /health attempts, last error: ${s??"n/a"}`)}let w=Date.now();if(w-g>=Go){let m=w-n,v=w-e.lastStdoutAt,Q=l>0?w-l:null;this.log.log(`[WorkerRuntimeManager] still waiting for /health on ${e.projectId} port ${r} (${(m/1000).toFixed(1)}s elapsed, tcpListening=${a}${Q!=null?`(${Q}ms ago)`:""}, lastStdout=${v}ms ago, ${c} http, ${u} tcp, lastError=${s??"n/a"})`),g=w}await new Promise((m)=>setTimeout(m,Ho))}if(o.exitCode!==null||o.signalCode!=null||o.killed)throw Error(`agent-runtime exited (code=${o.exitCode}, signal=${o.signalCode}) before becoming healthy on port ${r}`);throw Error(`Timeout waiting for agent-runtime /health on port ${r} after ${p} iter (httpAttempts=${c}, tcpAttempts=${u}, tcpListening=${a}, lastError=${s??"n/a"})`)}async waitForExit(e,t){if(e.exitCode!==null||e.signalCode!=null||e.killed)return;await new Promise((r)=>{let o=setTimeout(()=>{try{e.kill("SIGKILL")}catch{}r()},t);e.once("exit",()=>{clearTimeout(o),r()})})}snapshot(e){return{projectId:e.projectId,status:e.status,agentPort:e.agentPort||void 0,apiServerPort:e.apiServerPort||void 0,pid:e.proc?.pid,startedAt:e.startedAt||void 0,lastUsedAt:e.lastUsedAt,restarts:e.restarts,lastError:e.lastError}}}import{hostname as ze,platform as Qe,arch as Ze}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 er 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 ae=60,Te=300,et=3,Vo=et,Yo=1800000,Xo=25000,zt=1000,Qt=60000,Zt=3;class tt{opts;log;pollTimer=null;ws=null;wsIdleTimer=null;heartbeatTimer=null;stopped=!1;currentPollInterval=ae;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=ae,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 er;return new WebSocket(e,t)}getReconnectDelayMs(){let e=Math.min(zt*Math.pow(2,this.wsReconnectAttempt),Qt),t=e*0.2*Math.random();return e+t}async collectMetadata(){let e={hostname:ze(),os:Qe(),arch:Ze(),uptime:process.uptime(),protocolVersion:Zt,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=ze(),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:Qe(),arch:Ze(),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||ae;if(this.consecutiveAuthFailures>=et){if(this.consecutiveAuthSuccesses++,this.consecutiveAuthSuccesses<Vo){this.currentPollInterval=Te,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>=et){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!==Te)this.log.warn(`[WorkerTunnel] ${this.consecutiveAuthFailures} consecutive auth failures — `+`backing off to ${Te}s. Re-authenticate with \`shogo login\`.`);this.currentPollInterval=Te}else this.currentPollInterval=ae}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:l,value:c}=await s.read();if(l)break;if(this.ws?.readyState!==WebSocket.OPEN)break;this.sendFrame({type:"stream-chunk",requestId:e.requestId,data:a.decode(c,{stream:!0})})}if(this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-end",requestId:e.requestId})}catch(l){if(l?.name!=="AbortError"&&this.ws?.readyState===WebSocket.OPEN)this.sendFrame({type:"stream-error",requestId:e.requestId,error:l?.message??String(l)})}}else{let s=await i.text(),a={};i.headers.forEach((l,c)=>{a[c]=l}),this.sendFrame({type:"response",requestId:e.requestId,status:i.status,headers:a,body:s})}}catch(r){if(r?.name==="AbortError")return;if(this.ws?.readyState!==WebSocket.OPEN)return;let o=e.stream?{type:"stream-error",requestId:e.requestId,error:r?.message??String(r)}:{type:"response",requestId:e.requestId,status:502,body:JSON.stringify({error:r?.message??String(r)})};this.sendFrame(o)}finally{this.activeAbortControllers.delete(e.requestId)}}async resolveLocalUrl(e,t){return this.opts.resolver.resolveLocalUrl(e,t)}sendFrame(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return;try{this.ws.send(JSON.stringify(e))}catch(t){this.log.warn(`[WorkerTunnel] Frame send failed: ${t?.message??t}`)}}resetWsIdleTimer(){if(this.wsIdleTimer)clearTimeout(this.wsIdleTimer);this.wsIdleTimer=setTimeout(()=>{if(this.ws&&this.ws.readyState===WebSocket.OPEN){this.log.log("[WorkerTunnel] WebSocket idle timeout — closing, returning to polling");try{this.ws.close(1000,"Idle timeout")}catch{}}},Yo)}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{}},Xo)}connectWs(){if(this.stopped||this.ws)return;let e=this.buildWsUrl(),t=ze(),r=Qe(),o=Ze(),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 l;try{let c=typeof a.data==="string"?a.data:a.data.toString();l=JSON.parse(c)}catch{return}if(l.type==="ping"){this.sendFrame({type:"pong"}),this.resetWsIdleTimer();return}if(l.type==="cancel"){let c=this.activeAbortControllers.get(l.requestId);if(c)c.abort();return}if(l.type==="request"){this.handleRequest(l).catch((c)=>{this.log.error(`[WorkerTunnel] Error handling request: ${c?.message??c}`)});return}},s.onclose=(a)=>{if(this.log.log(`[WorkerTunnel] WebSocket closed: code=${a.code} reason=${a.reason||"none"}`),this.cleanupWs(),this.stopped)return;if(a.code===1000||a.code===4000)this.scheduleNextPoll(this.currentPollInterval);else{this.wsReconnectAttempt++;let l=this.getReconnectDelayMs();this.log.log(`[WorkerTunnel] Reconnecting in ${Math.round(l/1000)}s (attempt ${this.wsReconnectAttempt})`),this.scheduleNextPoll(Math.ceil(l/1000))}},s.onerror=(a)=>{this.log.error("[WorkerTunnel] WebSocket error:",a?.message??"unknown")}}cleanupWs(){if(this.heartbeatTimer)clearInterval(this.heartbeatTimer),this.heartbeatTimer=null;if(this.wsIdleTimer)clearTimeout(this.wsIdleTimer),this.wsIdleTimer=null;for(let[,e]of this.activeAbortControllers)try{e.abort()}catch{}this.activeAbortControllers.clear(),this.ws=null}_testing(){let e=this;return{sendHeartbeat:()=>e.sendHeartbeat(),heartbeatLoop:()=>e.heartbeatLoop(),connectWs:()=>e.connectWs(),cleanupWs:()=>e.cleanupWs(),handleRequest:(t)=>e.handleRequest(t),installFakeWs:(t)=>{e.ws=t},getCloudUrl:()=>e.getCloudUrl(),getWsBaseUrl:()=>e.getWsBaseUrl(),buildWsUrl:()=>e.buildWsUrl(),getReconnectDelayMs:()=>e.getReconnectDelayMs(),supportsWebSocketConstructorHeaders:(t)=>e.supportsWebSocketConstructorHeaders(t),createTunnelWebSocket:(t,r,o)=>e.createTunnelWebSocket(t,r,o),DEFAULT_POLL_INTERVAL_S:ae,BACKOFF_BASE_MS:zt,BACKOFF_MAX_MS:Qt,TUNNEL_PROTOCOL_VERSION:Zt,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 tr(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=bt(e.proxy);if(!e.foreground)return Qo({cfg:t,proxy:r,flags:e});let o=G({flag:e.runtimeBin});if(!o)console.error(h.red(J({flag:e.runtimeBin}))),process.exit(1);if(De(process.env,r),e.debug){if(!await Rt(Pt({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(`
14
+ 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"}},l=new Xe({runtimeBin:e.runtimeBin,defaultSpawnConfig:a,autoPull:{enabled:n,projectsDir:t.projectsDir,watch:!0,useGit:i}});if(!l.resolveBinary())console.error(h.red(J({flag:e.runtimeBin}))),process.exit(1);let c=new tt({apiKey:t.apiKey,cloudUrl:t.cloudUrl,name:t.name,kind:"cli-worker",resolver:l,onAuthRevoked:(g)=>{console.error(h.red(`✗ Cloud auth revoked: ${g}`)),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(g)=>{if(u)return;u=!0,console.log(h.dim(`
15
+ Received ${g} — shutting down...`));try{c.stop()}catch{}try{await l.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")),c.start(),console.log(h.green("✓ Worker running. Ctrl-C to stop.")),await new Promise(()=>{})}function Qo({cfg:e,proxy:t,flags:r}){let{entry:o,runner:n}=en(),i=De({...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=Zo(r),{pid:a}=wt({entry:o,runner:n,env:{...i,SHOGO_DETACHED_ARGS:s.join(" ")},cwd:e.workerDir,detach:!0,inheritStdio:!1});console.log(h.bold(`
16
+ 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 Zo(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 en(){let e=process.execPath,t=/\bbun(?:-[^/\\]*)?$/.test(e)||typeof globalThis.Bun<"u",r=process.argv[1];if(r&&zo(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 rr from"picocolors";async function or(){let{killedPid:e}=yt("SIGTERM");if(e===null){console.log(rr.dim("No worker running."));return}console.log(rr.green(`✓ Worker stopped (pid=${e}).`))}import N from"picocolors";async function nr(){let e=fe();if(!e){console.log(N.yellow("● stopped")+N.dim(" (no pid file)"));return}if(!we(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 tn,statSync as rn}from"node:fs";import ir from"picocolors";async function sr(e){let t=e.err?de:pe;if(!tn(t)){console.log(ir.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(rn(t).size===0)console.log(ir.dim("(log file is empty)"))}}import x from"picocolors";W();import{hostname as on,platform as nn,arch as sn}from"node:os";import B from"picocolors";class O extends Error{kind;constructor(e,t){super(e);this.kind=t;this.name="CloudLoginError"}}async function ar(e){let t=e.cloudUrl.replace(/\/$/,""),r=e.client??"cli",o=e.deviceName??on(),n=e.devicePlatform??`${nn()}-${sn()}`,i=e.appVersion??cn(),s=e.log??((m)=>console.log(m)),a=e.fetchImpl??fetch,l=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((m)=>{throw new O(`Cannot reach Shogo Cloud at ${t}: ${m?.message??m}`,"transport")});if(!l.ok){let m=await l.text().catch(()=>"");throw new O(`Cloud rejected /api/cli/login/start (HTTP ${l.status}): ${m||"no body"}`,"transport")}let c=await l.json().catch(()=>({}));if(!c?.ok||!c.state||!c.authUrl)throw new O(`Cloud returned a malformed start response: ${c?.error??JSON.stringify(c)}`,"transport");let u=an(e.pollIntervalMs??c.pollIntervalMs),p=e.timeoutMs??c.expiresInMs,g=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(c.userCode)),s(""),s(" Open this URL in your browser to approve:"),s(" "+B.cyan(c.authUrl)),s(""),e.openBrowser!==!1)if(typeof e.openBrowser==="function")try{await e.openBrowser(c.authUrl)}catch{}else un(c.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 O("Aborted by caller.","cancelled");if(Date.now()>=g)throw new O(`Timed out after ${Math.round(p/1000)}s waiting for approval.`,"timeout");let m=await a(`${t}/api/cli/login/poll?state=${encodeURIComponent(c.state)}`,{method:"GET",headers:{accept:"application/json","user-agent":V()},signal:AbortSignal.timeout(1e4)}).catch((v)=>(s(B.dim(` (poll error: ${v?.message??v} — retrying)`)),null));if(m&&m.ok){let v=await m.json().catch(()=>({}));if(v?.status==="approved"&&v.key)return{key:v.key,email:v.email??null,workspace:v.workspace??null,deviceId:v.deviceId??e.deviceId,workspaces:v.workspaces??null};if(v?.status==="denied")throw new O("Sign-in was denied in the browser.","denied");if(v?.status==="expired")throw new O("Sign-in request expired before it was approved.","expired")}await ln(u,w.signal)}}finally{if(f)process.removeListener("SIGINT",d),process.removeListener("SIGTERM",d)}}function an(e){if(!Number.isFinite(e)||e<=0)return 2000;return Math.min(Math.max(e,1000),1e4)}function ln(e,t){return new Promise((r,o)=>{if(t?.aborted)return o(new O("Aborted by caller.","cancelled"));let n=setTimeout(()=>{t?.removeEventListener("abort",i),r()},e),i=()=>{clearTimeout(n),o(new O("Aborted by caller.","cancelled"))};t?.addEventListener("abort",i,{once:!0})})}function cn(){return V()}function un(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 pn,writeFileSync as dn,existsSync as mn,chmodSync as gn}from"node:fs";import{randomUUID as hn}from"node:crypto";function lr(){if(mn(Z)){let t=pn(Z,"utf-8").trim();if(t)return t}H();let e=hn();return dn(Z,e,{mode:384}),gn(Z,384),e}var fn="https://studio.shogo.ai";async function cr(e){let t=L(),r=(e.cloudUrl||t.cloudUrl||fn).replace(/\/$/,""),o=e.apiKey||process.env.SHOGO_API_KEY;if(o){await wn({key:o,cloudUrl:r,cfg:t,name:e.name});return}let n=lr(),i;try{i=await ar({cloudUrl:r,deviceId:n,deviceName:e.name,workspaceId:e.workspace,openBrowser:!e.noBrowser})}catch(s){if(s instanceof O){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(oe(t),console.log(""),console.log(x.green("✓ Signed in to Shogo Cloud")),i.workspace)console.log(x.dim(" workspace: ")+i.workspace);if(i.email)console.log(x.dim(" email: ")+i.email);console.log(x.dim(" saved to: ")+"~/.shogo/config.json"),console.log(""),console.log(x.dim(" next: ")+"shogo worker start")}async function wn({key:e,cloudUrl:t,cfg:r,name:o}){if(!/^shogo_sk_/.test(e))console.error(x.red('✗ API key should start with "shogo_sk_". Copy it verbatim from the API Keys page.')),process.exit(1);let n;try{let i=await fetch(`${t}/api/api-keys/validate`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({key:e}),signal:AbortSignal.timeout(1e4)});if(n=await i.json().catch(()=>({})),!i.ok||!n?.valid)console.error(x.red(`✗ ${n?.error||`Cloud rejected the key (HTTP ${i.status}).`}`)),process.exit(1)}catch(i){console.error(x.red(`✗ Cannot reach Shogo Cloud at ${t}: ${i?.message??i}`)),process.exit(1)}if(r.apiKey=e,r.cloudUrl=t,o)r.name=o;if(oe(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 le from"picocolors";async function ur(){let e=L();if(Object.keys(e).length===0){console.log(le.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(le.dim(`file: ${F}`)),console.log(JSON.stringify(t,null,2))}async function pr(e,t){let r=["apiKey","cloudUrl","name","workerDir","port"];if(!r.includes(e))console.error(le.red(`Unknown key: ${e}`)),console.error(le.dim(`Allowed: ${r.join(", ")}`)),process.exit(1);let o=L();o[e]=e==="port"?parseInt(t,10):t,oe(o),console.log(le.green(`✓ ${e} set`))}import b from"picocolors";W();import{createHash as yn}from"node:crypto";import{createWriteStream as Sn,existsSync as ce,mkdirSync as rt,readFileSync as gr,renameSync as dr,rmSync as ot,writeFileSync as bn}from"node:fs";import{tmpdir as vn}from"node:os";import{dirname as kn,join as $e}from"node:path";import{Readable as Pn}from"node:stream";import{pipeline as Rn}from"node:stream/promises";var Tn="https://github.com/shogo-labs/shogo-ai/releases/download";function hr(){let e=process.platform,t=process.arch,r;if(e==="darwin")r="darwin";else if(e==="linux")r="linux";else if(e==="win32")r="windows";else throw Error(`Unsupported platform: ${e}`);let o;if(t==="arm64")o="arm64";else if(t==="x64")o="x64";else throw Error(`Unsupported arch: ${t} (need arm64 or x64)`);return`${r}-${o}`}function Ee(){if(!ce(re))return null;try{let e=gr(re,"utf-8");return JSON.parse(e)}catch{return null}}async function $n(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 g=await p.json();if(!g.tag_name)throw Error("GitHub API did not return tag_name");return mr(g.tag_name)}let i=`https://api.github.com/repos/${o}/${n}/releases?per_page=30`,s=await fetch(i,{headers:{Accept:"application/vnd.github+json"}});if(!s.ok)throw Error(`GitHub API ${s.status} for ${i}`);let a=await s.json(),l=e==="beta"?"-beta":"-nightly",c=a.find((u)=>u.prerelease&&/^v\d+\.\d+\.\d+-/.test(u.tag_name)&&u.tag_name.includes(l));if(!c)throw Error(`No ${e} runtime release found`);return mr(c.tag_name)}function mr(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 En(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 xn(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 Rn(Pn.fromWeb(r.body),Sn(t))}async function In(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 _n(e){let t=gr(e);return yn("sha256").update(t).digest("hex")}async function On(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 Cn(e,t){lt();let r=kn(t);if(!ce(r))rt(r,{recursive:!0,mode:448});let o=`${t}.next`;if(ce(o))ot(o,{force:!0});dr(e,o);try{if(process.platform!=="win32"){let{chmodSync:n}=Oe("node:fs");n(o,493)}}catch{}if(ce(t))ot(t,{force:!0});dr(o,t)}async function fr(e={}){let t=e.logger??console,r=e.channel??"stable",o=e.baseUrl??process.env.SHOGO_RUNTIME_RELEASES_URL??Tn,n=e.target??hr(),i=e.version;if(!i)t.log(`[runtime install] Resolving latest ${r} version...`),i=await $n(r,o),t.log(`[runtime install] Latest ${r} = ${i}`);let s=Ee();if(s&&s.version===i&&s.target===n&&!e.force)return t.log(`[runtime install] ${i} (${n}) already installed at ${A} — pass --force to reinstall`),{version:i,target:n,binPath:A,source:s.source,sha256:s.sha256,channel:r};let a=En(i,n,o);t.log(`[runtime install] Downloading ${a.tarball}`);let l=$e(vn(),`shogo-runtime-install-${process.pid}-${Date.now()}`);rt(l,{recursive:!0});try{let c=$e(l,a.assetName);await xn(a.tarball,c),t.log("[runtime install] Verifying SHA-256...");let u=await In(a.sha256),p=_n(c);if(p!==u)throw Error(`SHA-256 mismatch for ${a.assetName}
17
17
  expected: ${u}
18
- actual: ${p}`);let m=$e(l,"extract");et(m,{recursive:!0}),await xn(c,m);let f=$e(m,"agent-runtime");if(!ce(f))throw Error(`Tarball ${a.assetName} did not contain ./agent-runtime`);In(f,A);let d={version:i,target:n,installedAt:new Date().toISOString(),channel:r,source:a.tarball,sha256:p};return wn(re,JSON.stringify(d,null,2)+`
19
- `,{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{tt(l,{recursive:!0,force:!0})}catch{}}}function hr(){return{runtimeDir:te,runtimeBin:A,versionFile:re}}async function rt(e={}){let t=await gr({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 fr(){let e=Ee();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 wr(){let e=G(),t=hr();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(J()),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 yr(e={}){let t=Ee(),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 rt({channel:r,baseUrl:e.baseUrl,force:!0})}import{existsSync as _n,mkdirSync as Cn}from"node:fs";import{resolve as On}from"node:path";import y from"picocolors";import{CloudFileTransport as An}from"@shogo-ai/sdk/cloud-file-transport";async function Sr(e,t){if(!e)throw Error("projectId is required");let r=M({apiKey:t.apiKey,cloudUrl:t.cloudUrl});at(r.projectsDir);let o=On(t.into??Y(e,r.projectsDir));if(!_n(o))Cn(o,{recursive:!0});let n=t.include?.split(",").map((a)=>a.trim()).filter(Boolean);if(console.log(y.bold(`
20
- 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 An({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,include:n,onProgress:Mn()}),s=await i.downloadAll();if(Wn("Pull",s),t.watch){console.log(y.bold(`
21
- Watching for local changes...`));let a=new se({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 l=!1,c=async(u)=>{if(l)return;l=!0,console.log(y.dim(`
22
- 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 c("SIGINT")),process.once("SIGTERM",()=>void c("SIGTERM")),process.once("SIGHUP",()=>void c("SIGHUP")),await new Promise(()=>{})}}function Mn(){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(` (${Dn(e.bytes)})`):"";console.log(y.dim(` ${t}${r} ${e.path}${o}`))}}function Wn(e,t){let r=t.errors.length===0,o=r?y.green(`✓ ${e} complete`):y.red(`✗ ${e} completed with errors`);if(console.log(`
23
- ${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 Dn(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 Un}from"node:fs";import{resolve as Nn}from"node:path";import k from"picocolors";import{CloudFileTransport as jn}from"@shogo-ai/sdk/cloud-file-transport";async function br(e,t){if(!e)throw Error("projectId is required");let r=M({apiKey:t.apiKey,cloudUrl:t.cloudUrl}),o=Nn(t.from??Y(e,r.projectsDir));if(!Un(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(`
24
- 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 jn({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,include:n,onProgress:(a)=>{let l=a.kind==="upload"?"↑":a.kind==="delete"?"✗":"·",c=a.total>0?` ${a.index+1}/${a.total}`:"",u=a.bytes!=null?k.dim(` (${Hn(a.bytes)})`):"";console.log(k.dim(` ${l}${c} ${a.path}${u}`))}}).uploadAll({deleteRemote:t.deleteRemote});Fn("Push",s)}function Fn(e,t){let r=t.errors.length===0,o=r?k.green(`✓ ${e} complete`):k.red(`✗ ${e} completed with errors`);if(console.log(`
25
- ${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 Hn(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 Ln}from"node:fs";import{resolve as Gn}from"node:path";import D from"picocolors";ke();async function vr(e,t){if(!e)throw Error("projectId is required");let r=M({apiKey:t.apiKey,cloudUrl:t.cloudUrl}),o=Gn(t.into??Y(e,r.projectsDir));if(!Ln(o))throw Error(`Local project dir does not exist: ${o} — run \`shogo project pull\` first`);if(!ve(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(`
26
- 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 He({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o});if(!t.at){let i=await be({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 Bn(r.cloudUrl,r.apiKey,e,t.at),console.log(D.dim(` resolved checkpoint "${t.at}" → ${n.slice(0,8)}`))}try{await be({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 He({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o}),await be({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 Bn(e,t,r,o){let n=`${e.replace(/\/+$/,"")}/api/projects/${encodeURIComponent(r)}/checkpoints?limit=100`,i=await fetch(n,{headers:{Authorization:`Bearer ${t}`}});if(!i.ok)throw Error(`Failed to list checkpoints: HTTP ${i.status}`);let s=await i.json(),a=o.toLowerCase(),l=s.checkpoints.find((c)=>{if(c.name&&c.name.toLowerCase()===a)return!0;if(c.commitSha.startsWith(o))return!0;if(c.commitMessage&&c.commitMessage.toLowerCase().includes(a))return!0;return!1});if(!l)throw Error(`No checkpoint matches "${o}" (searched ${s.checkpoints.length} most recent)`);return l.commitSha}import{createInterface as Yn}from"node:readline";import S from"picocolors";W();import{existsSync as z,copyFileSync as kr}from"node:fs";import O from"node:path";import{homedir as Pr,platform as Rr}from"node:os";function Tr(e,t,r){let n=Me(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 xe(e,t){if(!z(t))return[];return Tr(e,t,`
18
+ actual: ${p}`);let g=$e(l,"extract");rt(g,{recursive:!0}),await On(c,g);let f=$e(g,"agent-runtime");if(!ce(f))throw Error(`Tarball ${a.assetName} did not contain ./agent-runtime`);Cn(f,A);let d={version:i,target:n,installedAt:new Date().toISOString(),channel:r,source:a.tarball,sha256:p};return bn(re,JSON.stringify(d,null,2)+`
19
+ `,{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{ot(l,{recursive:!0,force:!0})}catch{}}}function wr(){return{runtimeDir:te,runtimeBin:A,versionFile:re}}async function nt(e={}){let t=await fr({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 yr(){let e=Ee();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 Sr(){let e=G(),t=wr();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(J()),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 br(e={}){let t=Ee(),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 nt({channel:r,baseUrl:e.baseUrl,force:!0})}import{existsSync as An,mkdirSync as Mn}from"node:fs";import{resolve as Wn}from"node:path";import y from"picocolors";import{CloudFileTransport as Dn}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});ct(r.projectsDir);let o=Wn(t.into??Y(e,r.projectsDir));if(!An(o))Mn(o,{recursive:!0});let n=t.include?.split(",").map((a)=>a.trim()).filter(Boolean);if(console.log(y.bold(`
20
+ 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 Dn({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,include:n,onProgress:Un()}),s=await i.downloadAll();if(Nn("Pull",s),t.watch){console.log(y.bold(`
21
+ Watching for local changes...`));let a=new se({rootDir:o,transport:i,onFlush:({uploaded:u,errors:p})=>{let g=u.length===1?u[0]:`${u.length} files`,f=p>0?y.red(` (${p} errors)`):"";console.log(y.dim(` ↑ ${g}${f}`))}});a.start();let l=!1,c=async(u)=>{if(l)return;l=!0,console.log(y.dim(`
22
+ 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 c("SIGINT")),process.once("SIGTERM",()=>void c("SIGTERM")),process.once("SIGHUP",()=>void c("SIGHUP")),await new Promise(()=>{})}}function Un(){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(` (${jn(e.bytes)})`):"";console.log(y.dim(` ${t}${r} ${e.path}${o}`))}}function Nn(e,t){let r=t.errors.length===0,o=r?y.green(`✓ ${e} complete`):y.red(`✗ ${e} completed with errors`);if(console.log(`
23
+ ${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 jn(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 Fn}from"node:fs";import{resolve as Hn}from"node:path";import k from"picocolors";import{CloudFileTransport as Ln}from"@shogo-ai/sdk/cloud-file-transport";async function kr(e,t){if(!e)throw Error("projectId is required");let r=M({apiKey:t.apiKey,cloudUrl:t.cloudUrl}),o=Hn(t.from??Y(e,r.projectsDir));if(!Fn(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(`
24
+ 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 Ln({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o,include:n,onProgress:(a)=>{let l=a.kind==="upload"?"↑":a.kind==="delete"?"✗":"·",c=a.total>0?` ${a.index+1}/${a.total}`:"",u=a.bytes!=null?k.dim(` (${Bn(a.bytes)})`):"";console.log(k.dim(` ${l}${c} ${a.path}${u}`))}}).uploadAll({deleteRemote:t.deleteRemote});Gn("Push",s)}function Gn(e,t){let r=t.errors.length===0,o=r?k.green(`✓ ${e} complete`):k.red(`✗ ${e} completed with errors`);if(console.log(`
25
+ ${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 Bn(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 Kn}from"node:fs";import{resolve as Jn}from"node:path";import D from"picocolors";ke();async function Pr(e,t){if(!e)throw Error("projectId is required");let r=M({apiKey:t.apiKey,cloudUrl:t.cloudUrl}),o=Jn(t.into??Y(e,r.projectsDir));if(!Kn(o))throw Error(`Local project dir does not exist: ${o} — run \`shogo project pull\` first`);if(!ve(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(`
26
+ 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 Ge({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o});if(!t.at){let i=await be({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 qn(r.cloudUrl,r.apiKey,e,t.at),console.log(D.dim(` resolved checkpoint "${t.at}" → ${n.slice(0,8)}`))}try{await be({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 Ge({apiUrl:r.cloudUrl,apiKey:r.apiKey,projectId:e,localDir:o}),await be({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 qn(e,t,r,o){let n=`${e.replace(/\/+$/,"")}/api/projects/${encodeURIComponent(r)}/checkpoints?limit=100`,i=await fetch(n,{headers:{Authorization:`Bearer ${t}`}});if(!i.ok)throw Error(`Failed to list checkpoints: HTTP ${i.status}`);let s=await i.json(),a=o.toLowerCase(),l=s.checkpoints.find((c)=>{if(c.name&&c.name.toLowerCase()===a)return!0;if(c.commitSha.startsWith(o))return!0;if(c.commitMessage&&c.commitMessage.toLowerCase().includes(a))return!0;return!1});if(!l)throw Error(`No checkpoint matches "${o}" (searched ${s.checkpoints.length} most recent)`);return l.commitSha}import{createInterface as Qn}from"node:readline";import S from"picocolors";W();import{existsSync as z,copyFileSync as Rr}from"node:fs";import C from"node:path";import{homedir as Tr,platform as $r}from"node:os";function Er(e,t,r){let n=Me(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 xe(e,t){if(!z(t))return[];return Er(e,t,`
27
27
  import { Database } from 'bun:sqlite';
28
28
  try {
29
29
  const db = new Database(process.env.DBP, { readonly: true });
@@ -37,7 +37,7 @@ shogo project checkout ${D.cyan(e)}`)),console.log(D.dim(" cloud ")+r.cloudUrl
37
37
  console.error(String(e?.stack || e));
38
38
  process.exit(2);
39
39
  }
40
- `)}function Kn(e){if(!z(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}`);kr(e,n);for(let i of["-wal","-shm"]){let s=`${e}${i}`;if(z(s))kr(s,`${n}${i}`)}return n}function Jn(e,t,r){if(r.length===0)return 0;if(!z(t))throw Error(`Cannot repair: database does not exist at ${t}`);let n=`
40
+ `)}function Vn(e){if(!z(e))throw Error(`Database does not exist at ${e} — refusing to back up nothing`);let t=C.dirname(e),r=C.basename(e),o=new Date().toISOString().replace(/[:.]/g,"-"),n=C.join(t,`${r}.bak-${o}`);Rr(e,n);for(let i of["-wal","-shm"]){let s=`${e}${i}`;if(z(s))Rr(s,`${n}${i}`)}return n}function Yn(e,t,r){if(r.length===0)return 0;if(!z(t))throw Error(`Cannot repair: database does not exist at ${t}`);let n=`
41
41
  import { Database } from 'bun:sqlite';
42
42
  const names = ${JSON.stringify(r)};
43
43
  if (!Array.isArray(names) || names.some(n => typeof n !== 'string')) {
@@ -56,8 +56,8 @@ shogo project checkout ${D.cyan(e)}`)),console.log(D.dim(" cloud ")+r.cloudUrl
56
56
  console.error(String(e?.stack || e));
57
57
  process.exit(3);
58
58
  }
59
- `;return Tr(e,t,n).deleted}function $r(e){let t=e.log??(()=>{}),{bunPath:r,dbPath:o}=e;if(!z(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=xe(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=Kn(o),t(`Backed up database to ${i}`);let s=n.map((p)=>p.name),a=Jn(r,o,s);t(`Cleared ${a} failed migration row(s).`);let l=xe(r,o),c=l.length===0?"repaired":"failed",u=c==="repaired"?"Cleared the failed migration record. Relaunch Shogo to re-apply migrations cleanly.":`Repair incomplete — ${l.length} migration(s) still failed: ${l.map((p)=>p.name).join(", ")}.`;return{status:c,detected:n,backupPath:i,cleared:s.slice(0,a),remaining:l,message:u}}function qn(){let e=Pr(),t=Rr(),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 Er(){return O.join(qn(),"shogo.db")}function Vn(){let e=Rr(),t=e==="win32"?"bun.exe":"bun",r=Pr(),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 ot(e){try{return Me(e,["--version"],{stdio:"ignore",timeout:5000}),!0}catch{return!1}}function xr(e){if(e)return ot(e)?e:null;if(process.versions.bun&&process.execPath)return process.execPath;for(let t of Vn())if(z(t)&&ot(t))return t;if(ot("bun"))return"bun";return null}function Xn(e){for(let t of e){let r=Number.isFinite(t.startedAt)?new Date(t.startedAt).toISOString():"unknown time",o=(t.errorExcerpt??"").split(`
60
- `)[0]?.trim();if(console.log(` ${S.yellow("•")} ${S.bold(t.name)} ${S.dim(`(attempted ${r})`)}`),o)console.log(` ${S.dim(o)}`)}}async function zn(e){if(!process.stdin.isTTY)return console.log(S.dim("(stdin is not a TTY — re-run with --yes to repair non-interactively)")),!1;let t=Yn({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 Ir(e={}){let t=e.db??Er(),r=xr(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
61
- `+" (e.g. /Applications/Shogo.app/Contents/Resources/bun/bun on macOS).");console.log(S.dim(`Database: ${t}`)),console.log(S.dim(`Bun: ${r}`)),console.log();let o=xe(r,t);if(o.length===0){console.log(S.green("✓ No failed migrations detected — your local database looks healthy."));return}if(console.log(S.red(`✗ Found ${o.length} failed migration(s):`)),Xn(o),console.log(),e.check){console.log(S.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?S.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 zn(S.bold("Clear these failed migration records and repair? [y/N] "))){console.log(S.dim("Aborted — no changes made.")),process.exitCode=1;return}console.log()}let n=$r({bunPath:r,dbPath:t,skipBackup:e.backup===!1,log:(i)=>console.log(S.dim(` ${i}`))});if(console.log(),n.status==="repaired"){if(console.log(S.green(`✓ ${n.message}`)),n.backupPath)console.log(S.dim(` Backup: ${n.backupPath}`));console.log(S.bold(`
62
- → Relaunch the Shogo app to finish applying migrations.`))}else{if(console.log(S.red(`✗ ${n.message}`)),n.backupPath)console.log(S.dim(` Backup: ${n.backupPath}`));process.exitCode=1}}W();import{resolve as Qn}from"node:path";import _r from"picocolors";function Zn(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 Cr(e={}){let t=M({apiKey:e.apiKey,cloudUrl:e.cloudUrl}),r=G({flag:e.runtimeBin});if(!r)console.error(_r.red(J({flag:e.runtimeBin}))),process.exit(1);let o=Zn({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(_r.red(`✗ Failed to launch agent-runtime: ${a.message}`)),i(1)}),s.on("exit",(a)=>i(a??0))});process.exit(n)}var ti="0.1.0",U=new ei;U.name("shogo").description("Shogo Cloud Agent Worker — run Shogo agents on your own machine.").version(ti);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(()=>ar(e)));var Ie=U.command("worker").description("Manage the local worker process");Ie.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(()=>Zt(e)));Ie.command("stop").description("Stop the running worker").action(()=>P(tr));Ie.command("status").description("Show worker status").action(()=>P(rr));Ie.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(()=>nr(e)));var _e=U.command("runtime").description("Install / inspect the local agent-runtime binary");_e.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(()=>rt(e)));_e.command("version").description("Print the installed agent-runtime version").action(()=>P(()=>fr()));_e.command("where").description("Print the resolved agent-runtime binary path").action(()=>P(()=>wr()));_e.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(()=>yr(e)));var Ar=U.command("config").description("Inspect or modify ~/.shogo/config.json");Ar.command("show").description("Print current config (API key masked)").action(()=>P(lr));Ar.command("set <key> <value>").description("Set apiKey / cloudUrl / name / workerDir / port / projectsDir").action((e,t)=>P(()=>cr(e,t)));var nt=U.command("project").description("Clone/sync project workspaces between Shogo Cloud and this machine");nt.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(()=>Sr(e,t)));nt.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(()=>br(e,t)));nt.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(()=>vr(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(()=>Ir(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(()=>Cr({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(Or.dim(`
63
- (use --help for usage)`));U.parseAsync(process.argv);async function P(e){try{await e()}catch(t){if(console.error(Or.red(`✗ ${t?.message??t}`)),process.env.SHOGO_DEBUG)console.error(t?.stack);process.exit(1)}}
59
+ `;return Er(e,t,n).deleted}function xr(e){let t=e.log??(()=>{}),{bunPath:r,dbPath:o}=e;if(!z(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=xe(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=Vn(o),t(`Backed up database to ${i}`);let s=n.map((p)=>p.name),a=Yn(r,o,s);t(`Cleared ${a} failed migration row(s).`);let l=xe(r,o),c=l.length===0?"repaired":"failed",u=c==="repaired"?"Cleared the failed migration record. Relaunch Shogo to re-apply migrations cleanly.":`Repair incomplete — ${l.length} migration(s) still failed: ${l.map((p)=>p.name).join(", ")}.`;return{status:c,detected:n,backupPath:i,cleared:s.slice(0,a),remaining:l,message:u}}function Xn(){let e=Tr(),t=$r(),r;if(t==="darwin")r=C.join(e,"Library","Application Support");else if(t==="win32")r=process.env.APPDATA??C.join(e,"AppData","Roaming");else r=process.env.XDG_CONFIG_HOME??C.join(e,".config");return C.join(r,"Shogo","data")}function Ir(){return C.join(Xn(),"shogo.db")}function zn(){let e=$r(),t=e==="win32"?"bun.exe":"bun",r=Tr(),o=[];if(e==="darwin")o.push(C.join("/Applications","Shogo.app","Contents","Resources","bun",t),C.join(r,"Applications","Shogo.app","Contents","Resources","bun",t));else if(e==="linux")o.push(C.join("/opt","Shogo","resources","bun",t),C.join("/usr","lib","shogo","resources","bun",t));return o}function it(e){try{return Me(e,["--version"],{stdio:"ignore",timeout:5000}),!0}catch{return!1}}function _r(e){if(e)return it(e)?e:null;if(process.versions.bun&&process.execPath)return process.execPath;for(let t of zn())if(z(t)&&it(t))return t;if(it("bun"))return"bun";return null}function Zn(e){for(let t of e){let r=Number.isFinite(t.startedAt)?new Date(t.startedAt).toISOString():"unknown time",o=(t.errorExcerpt??"").split(`
60
+ `)[0]?.trim();if(console.log(` ${S.yellow("•")} ${S.bold(t.name)} ${S.dim(`(attempted ${r})`)}`),o)console.log(` ${S.dim(o)}`)}}async function ei(e){if(!process.stdin.isTTY)return console.log(S.dim("(stdin is not a TTY — re-run with --yes to repair non-interactively)")),!1;let t=Qn({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 Or(e={}){let t=e.db??Ir(),r=_r(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
61
+ `+" (e.g. /Applications/Shogo.app/Contents/Resources/bun/bun on macOS).");console.log(S.dim(`Database: ${t}`)),console.log(S.dim(`Bun: ${r}`)),console.log();let o=xe(r,t);if(o.length===0){console.log(S.green("✓ No failed migrations detected — your local database looks healthy."));return}if(console.log(S.red(`✗ Found ${o.length} failed migration(s):`)),Zn(o),console.log(),e.check){console.log(S.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?S.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 ei(S.bold("Clear these failed migration records and repair? [y/N] "))){console.log(S.dim("Aborted — no changes made.")),process.exitCode=1;return}console.log()}let n=xr({bunPath:r,dbPath:t,skipBackup:e.backup===!1,log:(i)=>console.log(S.dim(` ${i}`))});if(console.log(),n.status==="repaired"){if(console.log(S.green(`✓ ${n.message}`)),n.backupPath)console.log(S.dim(` Backup: ${n.backupPath}`));console.log(S.bold(`
62
+ → Relaunch the Shogo app to finish applying migrations.`))}else{if(console.log(S.red(`✗ ${n.message}`)),n.backupPath)console.log(S.dim(` Backup: ${n.backupPath}`));process.exitCode=1}}W();import{resolve as ti}from"node:path";import Cr from"picocolors";function ri(e){let{flags:t,config:r,runtime:o}=e,n=t.cwd?ti(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 Ar(e={}){let t=M({apiKey:e.apiKey,cloudUrl:e.cloudUrl}),r=G({flag:e.runtimeBin});if(!r)console.error(Cr.red(J({flag:e.runtimeBin}))),process.exit(1);let o=ri({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(Cr.red(`✗ Failed to launch agent-runtime: ${a.message}`)),i(1)}),s.on("exit",(a)=>i(a??0))});process.exit(n)}var ni="0.1.0",U=new oi;U.name("shogo").description("Shogo Cloud Agent Worker — run Shogo agents on your own machine.").version(ni);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(()=>cr(e)));var Ie=U.command("worker").description("Manage the local worker process");Ie.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(()=>tr(e)));Ie.command("stop").description("Stop the running worker").action(()=>P(or));Ie.command("status").description("Show worker status").action(()=>P(nr));Ie.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(()=>sr(e)));var _e=U.command("runtime").description("Install / inspect the local agent-runtime binary");_e.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(()=>nt(e)));_e.command("version").description("Print the installed agent-runtime version").action(()=>P(()=>yr()));_e.command("where").description("Print the resolved agent-runtime binary path").action(()=>P(()=>Sr()));_e.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(()=>br(e)));var Wr=U.command("config").description("Inspect or modify ~/.shogo/config.json");Wr.command("show").description("Print current config (API key masked)").action(()=>P(ur));Wr.command("set <key> <value>").description("Set apiKey / cloudUrl / name / workerDir / port / projectsDir").action((e,t)=>P(()=>pr(e,t)));var st=U.command("project").description("Clone/sync project workspaces between Shogo Cloud and this machine");st.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(()=>vr(e,t)));st.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(()=>kr(e,t)));st.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(()=>Pr(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(()=>Or(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(()=>Ar({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(Mr.dim(`
63
+ (use --help for usage)`));U.parseAsync(process.argv);async function P(e){try{await e()}catch(t){if(console.error(Mr.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.6-beta.20261020t083308",
3
+ "version": "2.3.7",
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.",
@@ -340,8 +340,8 @@ describe('gitFetchUnshallow', () => {
340
340
  });
341
341
  });
342
342
 
343
- describe('commitAndPush (author env vars)', () => {
344
- it('sets GIT_AUTHOR_EMAIL + GIT_COMMITTER_EMAIL when authorEmail is provided (L315-316)', async () => {
343
+ describe('commitAndPush (author identity)', () => {
344
+ it('always authors and commits as the canonical agent, whatever the caller asks for', async () => {
345
345
  spawnHandler = (inv) => {
346
346
  if (inv.args[0] === 'diff') return { exitCode: 1 };
347
347
  if (inv.args[0] === 'rev-parse') return { stdout: 'shaaaaaa\n' };
@@ -353,14 +353,51 @@ describe('commitAndPush (author env vars)', () => {
353
353
  projectId: 'p',
354
354
  localDir: '/tmp/never',
355
355
  message: 'feat: x',
356
- authorEmail: 'dev@shogo.ai',
356
+ // @ts-expect-error — the fields are gone; a caller that still passes them must not win.
357
+ authorEmail: 'dev2@shogo.local',
358
+ authorName: 'Shogo Dev 2',
357
359
  });
358
- const addInv = spawnInvocations.find((i) => i.args[0] === 'add')!;
359
- expect(addInv.env?.GIT_AUTHOR_EMAIL).toBe('dev@shogo.ai');
360
- expect(addInv.env?.GIT_COMMITTER_EMAIL).toBe('dev@shogo.ai');
360
+ for (const invocation of spawnInvocations) {
361
+ expect(invocation.env?.GIT_AUTHOR_NAME).toBe('Shogo Agent');
362
+ expect(invocation.env?.GIT_AUTHOR_EMAIL).toBe('agent@shogo.ai');
363
+ expect(invocation.env?.GIT_COMMITTER_NAME).toBe('Shogo Agent');
364
+ expect(invocation.env?.GIT_COMMITTER_EMAIL).toBe('agent@shogo.ai');
365
+ }
361
366
  });
362
367
 
363
- it('sets GIT_AUTHOR_NAME + GIT_COMMITTER_NAME when authorName is provided (L319-320)', async () => {
368
+ it('overrides a GIT_AUTHOR_* in the worker environment', async () => {
369
+ const previous = { name: process.env.GIT_AUTHOR_NAME, email: process.env.GIT_AUTHOR_EMAIL };
370
+ process.env.GIT_AUTHOR_NAME = 'Shogo Dev 3';
371
+ process.env.GIT_AUTHOR_EMAIL = 'dev3@shogo.local';
372
+ spawnInvocations.length = 0;
373
+ spawnHandler = (inv) => {
374
+ if (inv.args[0] === 'diff') return { exitCode: 1 };
375
+ if (inv.args[0] === 'rev-parse') return { stdout: 'shaaaaaa\n' };
376
+ return { exitCode: 0 };
377
+ };
378
+ try {
379
+ await commitAndPush({
380
+ apiUrl: 'https://api.shogo.ai',
381
+ apiKey: 'k',
382
+ projectId: 'p',
383
+ localDir: '/tmp/never',
384
+ message: 'feat: x',
385
+ });
386
+ const commitInv = spawnInvocations.find((i) => i.args[0] === 'commit')!;
387
+ expect(commitInv.env?.GIT_AUTHOR_EMAIL).toBe('agent@shogo.ai');
388
+ expect(commitInv.env?.GIT_COMMITTER_EMAIL).toBe('agent@shogo.ai');
389
+ expect(commitInv.env?.GIT_AUTHOR_NAME).toBe('Shogo Agent');
390
+ } finally {
391
+ if (previous.name === undefined) delete process.env.GIT_AUTHOR_NAME;
392
+ else process.env.GIT_AUTHOR_NAME = previous.name;
393
+ if (previous.email === undefined) delete process.env.GIT_AUTHOR_EMAIL;
394
+ else process.env.GIT_AUTHOR_EMAIL = previous.email;
395
+ }
396
+ });
397
+ });
398
+
399
+ describe('commitAndPush (co-author trailer)', () => {
400
+ it('writes the one canonical co-author', async () => {
364
401
  spawnHandler = (inv) => {
365
402
  if (inv.args[0] === 'diff') return { exitCode: 1 };
366
403
  if (inv.args[0] === 'rev-parse') return { stdout: 'shaaaaaa\n' };
@@ -371,12 +408,41 @@ describe('commitAndPush (author env vars)', () => {
371
408
  apiKey: 'k',
372
409
  projectId: 'p',
373
410
  localDir: '/tmp/never',
374
- message: 'feat: x',
375
- authorName: 'Shogo Bot',
411
+ message: 'chore: x',
376
412
  });
377
- const addInv = spawnInvocations.find((i) => i.args[0] === 'add')!;
378
- expect(addInv.env?.GIT_AUTHOR_NAME).toBe('Shogo Bot');
379
- expect(addInv.env?.GIT_COMMITTER_NAME).toBe('Shogo Bot');
413
+ const commitInv = spawnInvocations.find((i) => i.args[0] === 'commit')!;
414
+ const trailer = commitInv.args[commitInv.args.indexOf('--trailer') + 1];
415
+ expect(trailer).toBe('Co-authored-by: Shogo Agent <agent@shogo.ai>');
416
+ });
417
+
418
+ it('never takes the co-author from SHOGO_AGENT_GIT_* env overrides', async () => {
419
+ const previous = { name: process.env.SHOGO_AGENT_GIT_NAME, email: process.env.SHOGO_AGENT_GIT_EMAIL };
420
+ process.env.SHOGO_AGENT_GIT_NAME = 'Shogo Dev 3';
421
+ process.env.SHOGO_AGENT_GIT_EMAIL = 'dev3@shogo.local';
422
+ spawnInvocations.length = 0;
423
+ spawnHandler = (inv) => {
424
+ if (inv.args[0] === 'diff') return { exitCode: 1 };
425
+ if (inv.args[0] === 'rev-parse') return { stdout: 'shaaaaaa\n' };
426
+ return { exitCode: 0 };
427
+ };
428
+ try {
429
+ await commitAndPush({
430
+ apiUrl: 'https://api.shogo.ai',
431
+ apiKey: 'k',
432
+ projectId: 'p',
433
+ localDir: '/tmp/never',
434
+ message: 'chore: x',
435
+ });
436
+ const commitInv = spawnInvocations.find((i) => i.args[0] === 'commit')!;
437
+ expect(commitInv.args[commitInv.args.indexOf('--trailer') + 1]).toBe(
438
+ 'Co-authored-by: Shogo Agent <agent@shogo.ai>',
439
+ );
440
+ } finally {
441
+ if (previous.name === undefined) delete process.env.SHOGO_AGENT_GIT_NAME;
442
+ else process.env.SHOGO_AGENT_GIT_NAME = previous.name;
443
+ if (previous.email === undefined) delete process.env.SHOGO_AGENT_GIT_EMAIL;
444
+ else process.env.SHOGO_AGENT_GIT_EMAIL = previous.email;
445
+ }
380
446
  });
381
447
  });
382
448
 
@@ -47,9 +47,6 @@ export interface WatcherGitOptions {
47
47
  projectId: string;
48
48
  /** Branch to push to. Default `HEAD` (current). */
49
49
  branch?: string;
50
- /** Optional author identity for the auto-commits. */
51
- authorEmail?: string;
52
- authorName?: string;
53
50
  }
54
51
 
55
52
  /** Signature of `commitAndPush` from `./git-cloner`. Extracted so tests
@@ -62,8 +59,6 @@ export type CommitAndPushFn = (opts: {
62
59
  localDir: string;
63
60
  message: string;
64
61
  branch?: string;
65
- authorEmail?: string;
66
- authorName?: string;
67
62
  logger?: Pick<Console, 'log' | 'warn' | 'error'>;
68
63
  }) => Promise<{ committed: boolean; commitSha?: string }>;
69
64
 
@@ -301,8 +296,6 @@ export class CloudSyncWatcher {
301
296
  localDir: this.rootDir,
302
297
  message,
303
298
  branch: this.git.branch,
304
- authorEmail: this.git.authorEmail,
305
- authorName: this.git.authorName,
306
299
  logger: this.logger,
307
300
  });
308
301
  committed = res.committed;
@@ -27,6 +27,17 @@ import { promisify } from 'node:util';
27
27
 
28
28
  const execFileAsync = promisify(execFile);
29
29
 
30
+ /**
31
+ * The identity every Shogo commit is authored and committed as, and the one
32
+ * co-author trailer it carries. Never built from a caller-supplied identity:
33
+ * `dev2@shogo.local` produced commits whose author *and* co-author were gray on
34
+ * GitHub (no account behind either address). Kept as literals because this
35
+ * package is standalone MIT and cannot import `@shogo/shared-runtime` (AGPL).
36
+ */
37
+ const SHOGO_AGENT_NAME = 'Shogo Agent';
38
+ const SHOGO_AGENT_EMAIL = 'agent@shogo.ai';
39
+ const SHOGO_CO_AUTHOR_TRAILER = `Co-authored-by: ${SHOGO_AGENT_NAME} <${SHOGO_AGENT_EMAIL}>`;
40
+
30
41
  // ---------------------------------------------------------------------------
31
42
  // Availability probe
32
43
  // ---------------------------------------------------------------------------
@@ -286,10 +297,6 @@ export interface CommitAndPushOptions {
286
297
  message: string;
287
298
  /** Branch to push to. Defaults to `HEAD` (current branch). */
288
299
  branch?: string;
289
- /** Author email used for commit metadata. Falls back to GIT_AUTHOR_EMAIL. */
290
- authorEmail?: string;
291
- /** Author name. Falls back to GIT_AUTHOR_NAME. */
292
- authorName?: string;
293
300
  logger?: Pick<Console, 'log' | 'warn' | 'error'>;
294
301
  timeoutMs?: number;
295
302
  }
@@ -307,18 +314,18 @@ export interface CommitAndPushResult {
307
314
  * watcher doesn't claim a push for an empty edit batch.
308
315
  */
309
316
  export async function commitAndPush(opts: CommitAndPushOptions): Promise<CommitAndPushResult> {
310
- const { apiUrl, apiKey, projectId, localDir, message, branch = 'HEAD', authorEmail, authorName, logger, timeoutMs } = opts;
317
+ const { apiUrl, apiKey, projectId, localDir, message, branch = 'HEAD', logger, timeoutMs } = opts;
311
318
 
312
319
  const url = buildGitUrl(apiUrl, projectId);
313
- const env: NodeJS.ProcessEnv = {};
314
- if (authorEmail) {
315
- env.GIT_AUTHOR_EMAIL = authorEmail;
316
- env.GIT_COMMITTER_EMAIL = authorEmail;
317
- }
318
- if (authorName) {
319
- env.GIT_AUTHOR_NAME = authorName;
320
- env.GIT_COMMITTER_NAME = authorName;
321
- }
320
+ // Fixed here rather than taken from the caller or inherited from this
321
+ // process: a `GIT_AUTHOR_*` in the worker's environment would otherwise
322
+ // author the commit gray while its co-author line was already canonical.
323
+ const env: NodeJS.ProcessEnv = {
324
+ GIT_AUTHOR_NAME: SHOGO_AGENT_NAME,
325
+ GIT_AUTHOR_EMAIL: SHOGO_AGENT_EMAIL,
326
+ GIT_COMMITTER_NAME: SHOGO_AGENT_NAME,
327
+ GIT_COMMITTER_EMAIL: SHOGO_AGENT_EMAIL,
328
+ };
322
329
 
323
330
  await runGit(['add', '-A'], { cwd: localDir, env, logger, timeoutMs });
324
331
 
@@ -338,7 +345,7 @@ export async function commitAndPush(opts: CommitAndPushOptions): Promise<CommitA
338
345
  message,
339
346
  '--no-verify',
340
347
  '--trailer',
341
- `Co-authored-by: ${authorName || process.env.SHOGO_AGENT_GIT_NAME || 'Shogo Agent'} <${authorEmail || process.env.SHOGO_AGENT_GIT_EMAIL || 'agent@shogo.ai'}>`,
348
+ SHOGO_CO_AUTHOR_TRAILER,
342
349
  ],
343
350
  { cwd: localDir, env, logger, timeoutMs },
344
351
  );