vibemux-worker-preview 0.1.0-preview.1
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/bin/cli.mjs +8 -0
- package/dist-worker/apps/worker/src/index.js +165 -0
- package/package.json +24 -0
- package/runtime/worker-release.json +4 -0
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
|
|
5
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
6
|
+
const appRoot = path.resolve(scriptDir, '..')
|
|
7
|
+
process.env.VIBEMUX_RUNTIME_ROOT = appRoot
|
|
8
|
+
await import(path.join(appRoot, 'dist-worker', 'apps', 'worker', 'src', 'index.js'))
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import"dotenv/config";import{existsSync as Le,mkdirSync as Ge,readFileSync as He,rmSync as Gt,writeFileSync as Fe}from"node:fs";import{randomUUID as Ht}from"node:crypto";import Ve from"node:os";import M from"node:path";import{existsSync as fe,readFileSync as ge}from"node:fs";import C from"node:path";import{fileURLToPath as Nt}from"node:url";var ue=null,pe=null,me=null,Ut=e=>{try{let t=JSON.parse(ge(e,"utf8"));return!!(t.name&&t.version)}catch{return!1}},jt=()=>{let e=process.env.VIBEMUX_RUNTIME_ROOT?.trim();if(e)return C.resolve(e);let t=C.dirname(Nt(import.meta.url));for(;;){let r=C.join(t,"package.json");if(fe(r)&&Ut(r))return t;let o=C.dirname(t);if(o===t)return process.cwd();t=o}},I=()=>(ue||(ue=jt()),ue),Lt=()=>{if(!pe){let e=C.join(I(),"package.json");pe=fe(e)?JSON.parse(ge(e,"utf8")):{}}return pe},J=()=>{if(!me){let e=C.join(I(),"runtime","worker-release.json");me=fe(e)?JSON.parse(ge(e,"utf8")):{}}return me},U=()=>process.env.npm_package_version?.trim()||Lt().version?.trim()||"0.0.0",ke=()=>C.join(I(),"dist-worker","apps","worker","src","index.js"),Ne=()=>C.join(I(),"dist-worker","apps","worker","web"),Ue=()=>{let e=process.platform==="win32"?"vibemux-worker.cmd":"vibemux-worker";return C.join(I(),"bin",e)};var Ft=M.join(Ve.homedir(),".vibemux"),Vt=M.join(Ft,"worker"),Kt="http://127.0.0.1:8989",zt=()=>!!process.env.VIBEMUX_CLOUD_URL?.trim(),Ke=()=>process.env.VIBEMUX_CLOUD_URL?.trim()||J().defaultCloudUrl?.trim()||Kt,qt=()=>{let e=process.env.VIBEMUX_WORKER_HOME?.trim();if(e)return e;let t=process.env.VIBEMUX_HOME?.trim();return t?M.join(t,"worker"):Vt},B=()=>M.resolve(qt()),he=()=>M.join(B(),"config.json"),Jt=()=>M.join(B(),"machine-id"),Qt=()=>{let e=B(),t=Jt();if(Le(t)){let o=He(t,"utf8").trim();if(o)return o}Ge(e,{recursive:!0});let r=Ht();return Fe(t,`${r}
|
|
2
|
+
`,"utf8"),r},je=()=>({cloudUrl:Ke(),machineId:Qt(),machineName:Ve.hostname(),opencodeConfigContent:"",defaultModel:"",workspaceRoot:M.join(B(),"workspace"),maxConcurrency:1,labels:[],capabilities:["code-execution","git-operations"],localServerPort:Number(process.env.VIBEMUX_WORKER_PORT||48100),projectBindings:[]}),m=()=>{let e=he();if(!Le(e))return je();let t=JSON.parse(He(e,"utf8")),r={...je(),...t};return zt()&&(r.cloudUrl=Ke()),r},G=e=>{let t=B();Ge(t,{recursive:!0}),Fe(he(),`${JSON.stringify(e,null,2)}
|
|
3
|
+
`,"utf8")},Q=()=>{Gt(he(),{force:!0})};import{spawn as Ko}from"node:child_process";var ye=e=>e.replace(/\/+$/,""),Xt=e=>{let t=ye(e);return t.startsWith("https://")?`${t.replace("https://","wss://")}/api/control-plane/executors/ws`:t.startsWith("http://")?`${t.replace("http://","ws://")}/api/control-plane/executors/ws`:`${t}/api/control-plane/executors/ws`},ze=async(e,t)=>{let r=await fetch(`${ye(t)}/api/control-plane/executors/pair`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!r.ok){let o=await r.json().catch(()=>({message:"Pair request failed."}));throw new Error(o.message||"Pair request failed.")}return await r.json()},qe=(e,t)=>{if(!e.executorToken)throw new Error("Worker is not paired yet.");let r=`${Xt(e.cloudUrl)}?token=${encodeURIComponent(e.executorToken)}`,o=new WebSocket(r);return o.addEventListener("open",()=>{t.onOpen?.()}),o.addEventListener("message",async n=>{let i="";try{typeof n.data=="string"?i=n.data:n.data instanceof ArrayBuffer?i=Buffer.from(n.data).toString("utf8"):typeof Blob<"u"&&n.data instanceof Blob?i=await n.data.text():n.data&&typeof n.data.toString=="function"&&(i=n.data.toString());let s=JSON.parse(i);try{t.onMessage?.(s)}catch(a){t.onError?.(`\u63A7\u5236\u9762\u6D88\u606F\u5904\u7406\u5931\u8D25\u3002${a instanceof Error?` ${a.message}`:""}`)}}catch(s){t.onError?.(`\u63A7\u5236\u9762\u8FD4\u56DE\u4E86\u65E0\u6CD5\u89E3\u6790\u7684\u6D88\u606F\u3002${s instanceof Error?` ${s.message}`:""}`)}}),o.addEventListener("error",()=>{t.onError?.(`\u65E0\u6CD5\u8FDE\u63A5\u5230 \u2601\uFE0F \u4E91\u7AEF WebSocket: ${r}`)}),o.addEventListener("close",n=>{t.onClose?.(n)}),{socket:o,send(n){o.send(JSON.stringify(n))}}},Je=async e=>{let t=await fetch(`${ye(e.cloudUrl)}/api/control-plane/executors/artifacts/patch`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.executorToken}`},body:JSON.stringify({taskId:e.taskId,filename:e.filename,content:e.content})});if(!t.ok){let r=await t.json().catch(()=>({message:"Patch artifact upload failed."}));throw new Error(r.message||"Patch artifact upload failed.")}return await t.json()};import{existsSync as fr,readFileSync as gr}from"node:fs";import kr from"node:http";import Se from"node:path";import{spawnSync as Yt}from"node:child_process";import{existsSync as we}from"node:fs";var H=900*1e3,P=I(),W=(e,t,r)=>{let o=Yt(e,t,{cwd:r?.cwd,encoding:"utf8",timeout:r?.timeout??12e4});return{ok:o.status===0,stdout:o.stdout?.trim()??"",stderr:o.stderr?.trim()??"",error:o.error instanceof Error?o.error.message:void 0}},A=(e,t)=>e.stdout||e.stderr||e.error||t,T=(e,t=["--version"])=>W(e,t).ok,Xe=()=>{let e=W("git",["--version"]);return{id:"git",ok:e.ok,detail:A(e,"git \u4E0D\u53EF\u7528")}},Ye=()=>{let e=W(process.execPath,["-e",'import("@opencode-ai/sdk").then(() => console.log("ok")).catch((error) => { console.error(error instanceof Error ? error.message : String(error)); process.exit(1) })'],{cwd:P});return{id:"opencode",ok:e.ok,detail:e.ok?"@opencode-ai/sdk \u5DF2\u5B89\u88C5":A(e,"OpenCode SDK \u4E0D\u53EF\u7528")}},Zt=(e,t)=>typeof process.getuid=="function"&&process.getuid()===0?{command:e,args:t}:T("sudo",["-n","true"])?{command:"sudo",args:["-n",e,...t]}:null,j=(e,t)=>{let r=Zt(e,t);if(!r)return{ok:!1,changed:!1,detail:"\u7F3A\u5C11 root \u6216\u514D\u5BC6 sudo \u6743\u9650\uFF0C\u65E0\u6CD5\u81EA\u52A8\u5B89\u88C5 git\u3002"};let o=W(r.command,r.args,{timeout:H});return{ok:o.ok,changed:o.ok,detail:A(o,"git \u81EA\u52A8\u5B89\u88C5\u5931\u8D25")}},er=()=>{if(Xe().ok)return{ok:!0,changed:!1,detail:"git \u5DF2\u5C31\u7EEA"};if(process.platform==="darwin"){if(!T("brew"))return{ok:!1,changed:!1,detail:"\u672A\u68C0\u6D4B\u5230 Homebrew\uFF0C\u5F53\u524D\u65E0\u6CD5\u81EA\u52A8\u5B89\u88C5 git\u3002"};let e=W("brew",["install","git"],{timeout:H});return{ok:e.ok,changed:e.ok,detail:A(e,"brew install git \u5931\u8D25")}}if(process.platform==="linux"){if(T("apt-get")){let e=j("apt-get",["update"]);return e.ok?j("apt-get",["install","-y","git"]):e}if(T("dnf"))return j("dnf",["install","-y","git"]);if(T("yum"))return j("yum",["install","-y","git"]);if(T("pacman"))return j("pacman",["-Sy","--noconfirm","git"]);if(T("apk"))return j("apk",["add","git"])}return{ok:!1,changed:!1,detail:`\u5F53\u524D\u5E73\u53F0 ${process.platform} \u6682\u672A\u5B9E\u73B0 git \u81EA\u52A8\u5B89\u88C5\u3002`}},tr=()=>{if(Ye().ok)return{ok:!0,changed:!1,detail:"OpenCode SDK \u5DF2\u5C31\u7EEA"};if(!we(`${P}/package.json`))return{ok:!1,changed:!1,detail:`\u672A\u627E\u5230 package.json\uFF0C\u65E0\u6CD5\u81EA\u52A8\u5B89\u88C5 OpenCode SDK: ${P}`};if(we(`${P}/pnpm-lock.yaml`)&&T("pnpm")){let e=W("pnpm",["install","--frozen-lockfile"],{cwd:P,timeout:H});return{ok:e.ok,changed:e.ok,detail:A(e,"pnpm install \u5931\u8D25")}}if(we(`${P}/package-lock.json`)&&T("npm")){let e=W("npm",["ci"],{cwd:P,timeout:H});return{ok:e.ok,changed:e.ok,detail:A(e,"npm ci \u5931\u8D25")}}if(T("npm")){let e=W("npm",["install"],{cwd:P,timeout:H});return{ok:e.ok,changed:e.ok,detail:A(e,"npm install \u5931\u8D25")}}return{ok:!1,changed:!1,detail:"\u672A\u68C0\u6D4B\u5230 pnpm/npm\uFF0C\u65E0\u6CD5\u81EA\u52A8\u5B89\u88C5 OpenCode SDK\u3002"}},Qe=()=>{let e=[Xe(),Ye()],t=e.every(r=>r.ok);return{ok:t,changed:!1,items:e,message:t?"Git \u4E0E OpenCode \u8FD0\u884C\u65F6\u5DF2\u5C31\u7EEA\u3002":`\u8FD0\u884C\u73AF\u5883\u7F3A\u5931\uFF1A${e.filter(r=>!r.ok).map(r=>r.id).join("\u3001")}`}},v=async e=>{let t=Qe();if(t.ok||!e?.autoInstall)return t;let r=new Map,o=!1;for(let s of t.items){if(s.ok)continue;let a=s.id==="git"?er():tr();r.set(s.id,a.detail),o=o||a.changed}let n=Qe(),i=n.items.map(s=>{let a=r.get(s.id);return a?{...s,detail:`${s.detail}\uFF1B\u81EA\u52A8\u51C6\u5907\uFF1A${a}`}:s});return{ok:n.ok,changed:o,items:i,message:n.ok?o?"\u5DF2\u81EA\u52A8\u8865\u9F50 Worker \u8FD0\u884C\u73AF\u5883\u3002":"Git \u4E0E OpenCode \u8FD0\u884C\u65F6\u5DF2\u5C31\u7EEA\u3002":`\u81EA\u52A8\u51C6\u5907\u5931\u8D25\uFF1A${i.filter(s=>!s.ok).map(s=>s.id).join("\u3001")}`}};var X={daemonMode:"idle",paired:!1,connected:!1,runningTaskIds:[],queuedTaskIds:[]},R=()=>({...X,runningTaskIds:[...X.runningTaskIds],queuedTaskIds:[...X.queuedTaskIds]}),y=e=>{Object.assign(X,e)};var Y=null,rr=()=>Y||(Y=J(),Y),or=()=>process.env.VIBEMUX_WORKER_RELEASE_CHANNEL?.trim()||rr().channel?.trim()||"production",Z=async()=>{let e=U(),t=or();return{ok:!0,currentVersion:e,channel:t,available:!1,message:"\u5F53\u524D npm Worker \u4E0D\u63D0\u4F9B\u5185\u7F6E\u81EA\u66F4\u65B0\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C npx \u83B7\u53D6\u65B0\u7248\u672C\u3002"}};import{existsSync as Ze,mkdirSync as et,readdirSync as nr,renameSync as xe,rmSync as be,writeFileSync as sr}from"node:fs";import ir from"node:os";import O from"node:path";import{spawn as tt,spawnSync as ar}from"node:child_process";var cr=e=>new Promise(t=>setTimeout(t,e)),dr=()=>Ze(Ue())&&Ze(ke()),lr=async e=>{for(let t=0;t<120;t+=1)try{process.kill(e,0),await cr(500)}catch{return}throw new Error(`\u7B49\u5F85\u65E7 Worker \u9000\u51FA\u8D85\u65F6: ${e}`)},ur=async e=>{let t=await fetch(e,{method:"GET",signal:AbortSignal.timeout(3e4)});if(!t.ok)throw new Error(`\u4E0B\u8F7D\u66F4\u65B0\u5305\u5931\u8D25\uFF0CHTTP ${t.status}`);let r=Buffer.from(await t.arrayBuffer()),o=O.join(ir.tmpdir(),"vibemux-worker-updates");et(o,{recursive:!0});let n=O.join(o,`worker-${Date.now()}.tar.gz`);return sr(n,r),n},pr=(e,t)=>{let r=ar("tar",["-xzf",e,"-C",t],{encoding:"utf8"});if(r.status!==0)throw new Error(r.stderr?.trim()||r.error?.message||"\u89E3\u538B\u66F4\u65B0\u5305\u5931\u8D25")},mr=e=>{tt(O.join(e,"bin","vibemux-worker"),["daemon"],{detached:!0,stdio:"ignore"}).unref()},F=async()=>{let e=await Z();if(!e.ok||!e.available||!e.asset)return{ok:e.ok,applied:!1,currentVersion:e.currentVersion,latestVersion:e.latestVersion,message:e.message};if(!dr())return{ok:!1,applied:!1,currentVersion:e.currentVersion,latestVersion:e.latestVersion,message:"\u5F53\u524D\u4E0D\u662F\u4FBF\u643A Worker \u5B89\u88C5\u76EE\u5F55\uFF0C\u65E0\u6CD5\u6267\u884C\u81EA\u66F4\u65B0\u3002"};let t=await ur(e.asset.url);return tt(process.execPath,[ke(),"apply-update-internal",t,I(),String(process.pid)],{detached:!0,stdio:"ignore"}).unref(),{ok:!0,applied:!0,currentVersion:e.currentVersion,latestVersion:e.latestVersion,message:`\u5DF2\u5F00\u59CB\u66F4\u65B0\u5230 ${e.latestVersion}\uFF0CWorker \u5373\u5C06\u91CD\u542F\u3002`}},rt=async()=>{let e=process.env.VIBEMUX_WORKER_AUTO_UPDATE?.trim();if(e!=="1"&&e!=="true")return!1;let t=await F();return t.applied?(console.log(`[worker] ${t.message}`),setTimeout(()=>process.exit(0),250),!0):!1},ot=async(e,t,r)=>{await lr(r);let o=O.dirname(t),n=O.join(o,`.vibemux-worker-stage-${Date.now()}`),i=O.join(o,`.vibemux-worker-backup-${Date.now()}`);et(n,{recursive:!0}),pr(e,n);let[s]=nr(n);if(!s)throw new Error("\u66F4\u65B0\u5305\u5185\u5BB9\u4E3A\u7A7A");let a=O.join(n,s);xe(t,i);try{xe(a,t)}catch(c){throw xe(i,t),c}mr(t),be(i,{recursive:!0,force:!0}),be(n,{recursive:!0,force:!0}),be(e,{force:!0})};var hr=e=>new URL(e.url||"/","http://127.0.0.1"),nt=async e=>{let t=[];for await(let r of e)t.push(Buffer.isBuffer(r)?r:Buffer.from(r));return JSON.parse(Buffer.concat(t).toString("utf8"))},w=(e,t,r)=>{e.statusCode=t,e.setHeader("Content-Type","application/json; charset=utf-8"),e.end(`${JSON.stringify(r)}
|
|
4
|
+
`)},yr=(e,t)=>{let r=Se.extname(t),o=r===".css"?"text/css; charset=utf-8":r===".js"?"application/javascript; charset=utf-8":r===".map"?"application/json; charset=utf-8":"text/html; charset=utf-8";e.statusCode=200,e.setHeader("Content-Type",o),e.end(gr(t))},wr=e=>{let t=Ne(),r=e==="/"?Se.join(t,"index.html"):Se.join(t,e.replace(/^\//,""));return r.startsWith(t)?r:null},xr=(e,t)=>{let r=wr(e);if(!r||!fr(r)){w(t,404,{message:"not found"});return}yr(t,r)},br=()=>{let e=R(),t=m();return{ok:!0,service:"worker-local-server",daemonMode:e.daemonMode,paired:e.paired,connected:e.connected,executorId:e.executorId,runningTaskIds:e.runningTaskIds,queuedTaskIds:e.queuedTaskIds,localServerPort:t.localServerPort}},Ce=()=>{let e=m(),t=kr.createServer(async(r,o)=>{try{let n=hr(r);if(r.method==="GET"&&(n.pathname==="/"||n.pathname.startsWith("/assets/"))){xr(n.pathname,o);return}if(r.method==="GET"&&(n.pathname==="/health"||n.pathname==="/api/health")){w(o,200,br());return}if(r.method==="GET"&&n.pathname==="/api/config"){w(o,200,{config:m()});return}if(r.method==="GET"&&n.pathname==="/api/status"){w(o,200,{runtime:R()});return}if(r.method==="GET"&&n.pathname==="/api/doctor"){w(o,200,await ee());return}if(r.method==="GET"&&n.pathname==="/api/update"){w(o,200,await Z());return}if(r.method==="POST"&&n.pathname==="/api/bootstrap-runtime"){let i=await v({autoInstall:!0});w(o,200,{report:i,doctor:await ee()});return}if(r.method==="PUT"&&n.pathname==="/api/config"){let i=await nt(r),s={...m(),...i};G(s),y({config:s,paired:!!(s.executorId&&s.executorToken),executorId:s.executorId}),w(o,200,{config:s,message:"saved"});return}if(r.method==="POST"&&n.pathname==="/api/pair"){let i=await nt(r),s=m(),a=await ze({pairingCode:i.pairingCode,machineId:s.machineId,machineName:s.machineName,name:i.name?.trim()||`worker-${process.pid}`,workspaceRoot:s.workspaceRoot,maxConcurrency:s.maxConcurrency,labels:s.labels,capabilities:s.capabilities,platform:process.platform,version:U()},s.cloudUrl),c={...s,executorName:i.name?.trim()||`worker-${process.pid}`,executorId:a.executorId,executorToken:a.executorToken};G(c),y({paired:!0,executorId:a.executorId,config:c,daemonMode:"starting"}),Te(),w(o,200,{config:c,executor:a.executor,message:"Pairing complete. Connecting to the control plane."});return}if(r.method==="POST"&&n.pathname==="/api/reset"){Q(),y({daemonMode:"idle",paired:!1,connected:!1,executorId:void 0,config:m(),runningTaskIds:[],queuedTaskIds:[],lastError:void 0}),w(o,200,{config:m(),message:"reset"});return}if(r.method==="POST"&&n.pathname==="/api/disconnect"){st(),w(o,200,{runtime:R(),message:"Disconnected from the control plane."});return}if(r.method==="POST"&&n.pathname==="/api/connect"){Te(),w(o,200,{runtime:R(),message:"Control plane connection requested."});return}if(r.method==="POST"&&n.pathname==="/api/update"){let i=await F();w(o,i.applied?202:200,i),i.applied&&setTimeout(()=>process.exit(0),250);return}w(o,404,{message:"not found"})}catch(n){w(o,500,{message:n instanceof Error?n.message:"worker local server error"})}});return t.listen(e.localServerPort,"127.0.0.1",()=>{console.log(`[worker] local console http://127.0.0.1:${e.localServerPort}`)}),t};import{mkdirSync as Sr}from"node:fs";import V from"node:path";var te=e=>{let t={root:e,reposDir:V.join(e,"repos"),worktreesDir:V.join(e,"worktrees"),artifactsDir:V.join(e,"artifacts"),cacheDir:V.join(e,"cache")};for(let r of Object.values(t))Sr(r,{recursive:!0});return t},it=(e,t)=>V.join(e,"worktrees",t);import{accessSync as Tr,constants as Cr,mkdirSync as Ir}from"node:fs";var Rr=e=>{try{return Ir(e,{recursive:!0}),Tr(e,Cr.W_OK),{ok:!0,detail:`\u53EF\u5199: ${e}`}}catch(t){return{ok:!1,detail:t instanceof Error?t.message:"path access failed"}}},Er=e=>{try{return{ok:!0,detail:`\u5DF2\u5C31\u7EEA: ${te(e).root}`}}catch(t){return{ok:!1,detail:t instanceof Error?t.message:"workspace setup failed"}}},Pr=async e=>{try{let t=await fetch(e,{method:"GET",signal:AbortSignal.timeout(5e3)});return{ok:!0,status:t.status,message:`\u63A7\u5236\u9762\u53EF\u8FBE\uFF0CHTTP ${t.status}`}}catch(t){return{ok:!1,status:void 0,message:t instanceof Error?t.message:"cloud probe failed"}}},Wr=async()=>{try{let e=await fetch("https://opencode.ai",{method:"GET",signal:AbortSignal.timeout(5e3)});return{ok:e.ok,status:e.status,url:"https://opencode.ai",message:e.ok?`\u5B98\u7F51\u53EF\u8FBE\uFF0CHTTP ${e.status}`:`\u5B98\u7F51\u8FD4\u56DE\u5F02\u5E38\uFF0CHTTP ${e.status}`}}catch(e){return{ok:!1,status:void 0,url:"https://opencode.ai",message:e instanceof Error?e.message:"official site probe failed"}}},vr=e=>{let t=e.filter(o=>o.ok).length,r=e.length-t;return{total:e.length,passed:t,failed:r,ok:r===0}},at=async()=>{let e=m(),[t,r,o]=await Promise.all([Pr(e.cloudUrl),Wr(),v()]),n=Rr(B()),i=Er(e.workspaceRoot),s=o.items.find(l=>l.id==="git")??{ok:!1,detail:"git \u72B6\u6001\u672A\u77E5"},a=o.items.find(l=>l.id==="opencode")??{ok:!1,detail:"OpenCode SDK \u72B6\u6001\u672A\u77E5"},c=[{id:"git",category:"tooling",label:"Git",ok:s.ok,detail:s.detail,hint:s.ok?void 0:"\u8BF7\u5148\u5B89\u88C5 git\uFF0C\u5E76\u786E\u8BA4\u5B83\u5728 PATH \u4E2D\u53EF\u6267\u884C\u3002"},{id:"opencode",category:"tooling",label:"OpenCode SDK",ok:a.ok,detail:a.detail,hint:a.ok?void 0:"\u68C0\u67E5\u4F9D\u8D56\u5B89\u88C5\u72B6\u6001\uFF0C\u786E\u8BA4 `@opencode-ai/sdk` \u53EF\u88AB\u5F53\u524D Node \u8FD0\u884C\u65F6\u52A0\u8F7D\u3002"},{id:"worker-home",category:"filesystem",label:"Worker Home",ok:n.ok,detail:n.detail,hint:n.ok?void 0:"\u8BF7\u68C0\u67E5\u76EE\u5F55\u6743\u9650\uFF0C\u786E\u4FDD worker home \u53EF\u521B\u5EFA\u4E14\u53EF\u5199\u3002"},{id:"workspace",category:"filesystem",label:"Workspace",ok:i.ok,detail:i.detail,hint:i.ok?void 0:"\u8BF7\u786E\u8BA4 workspace root \u8DEF\u5F84\u6709\u6548\uFF0C\u5E76\u5141\u8BB8\u5F53\u524D\u7528\u6237\u5199\u5165\u3002"},{id:"machine-id",category:"config",label:"Machine ID",ok:!!e.machineId,detail:e.machineId?`\u5DF2\u914D\u7F6E: ${e.machineId}`:"\u672A\u914D\u7F6E machineId",hint:e.machineId?void 0:"\u91CD\u7F6E\u672C\u5730\u914D\u7F6E\u540E\u91CD\u65B0\u542F\u52A8 worker\uFF0C\u53EF\u81EA\u52A8\u751F\u6210 machineId\u3002"},{id:"pairing",category:"config",label:"Pairing",ok:!!(e.executorId&&e.executorToken),detail:e.executorId?`\u5DF2\u914D\u5BF9: ${e.executorId}`:"\u672A\u5B8C\u6210 pairing",hint:e.executorId?void 0:"\u5728\u5DE5\u4F5C\u7AD9\u63A7\u5236\u53F0\u4E2D\u8F93\u5165 pairing code \u5B8C\u6210\u914D\u5BF9\u3002"},{id:"cloud-url",category:"network",label:"Control Plane URL",ok:!!e.cloudUrl?.trim(),detail:e.cloudUrl?.trim()||"\u672A\u914D\u7F6E cloudUrl",hint:e.cloudUrl?.trim()?void 0:"\u8BF7\u5148\u586B\u5199\u63A7\u5236\u9762\u7684 Cloud URL\u3002"},{id:"cloud-reachable",category:"network",label:"Control Plane Reachability",ok:t.ok,detail:t.message,hint:t.ok?void 0:"\u8BF7\u786E\u8BA4\u63A7\u5236\u9762\u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u4E14\u5F53\u524D\u673A\u5668\u53EF\u4EE5\u8BBF\u95EE\u8BE5\u5730\u5740\u3002"},{id:"official-site",category:"network",label:"OpenCode \u5B98\u7F51",ok:r.ok,detail:r.message,hint:r.ok?void 0:"\u5982\u679C\u5B98\u7F51\u4E0D\u53EF\u8FBE\uFF0C\u901A\u5E38\u610F\u5473\u7740\u5F53\u524D\u7F51\u7EDC\u5B58\u5728\u5916\u7F51\u8BBF\u95EE\u9650\u5236\u3002"}];return{config:e,checks:{git:s.ok,opencodeAvailable:a.ok,workerHomeWritable:n.ok,workspaceConfigured:!!e.workspaceRoot,workspaceReady:i.ok,machineIdConfigured:!!e.machineId,paired:!!(e.executorId&&e.executorToken),cloudUrlConfigured:!!e.cloudUrl?.trim(),cloudReachable:t.ok,officialSiteReachable:r.ok},items:c,summary:vr(c),cloudProbe:t,officialSiteProbe:r,runtime:R()}},Ie=async()=>{console.log(JSON.stringify(await at(),null,2))},ee=()=>at();import{readdir as Dr,stat as Re}from"node:fs/promises";import ct from"node:os";import _ from"node:path";import{simpleGit as oe}from"simple-git";var $="main",Mr=e=>{let t=e.trim();return t?t==="~"?ct.homedir():t.startsWith("~/")?_.join(ct.homedir(),t.slice(2)):t:""},re=e=>_.resolve(Mr(e)),Br=(e,t)=>{let r=_.relative(e,t);return r===""||!r.startsWith("..")&&!_.isAbsolute(r)},Ar=(e,t)=>{let r=re(e),o=t?.trim()?re(t):r;return{resolvedRootPath:r,requestedPath:o}},E=e=>{let t=e.trim();if(!t||t==="HEAD"||t.endsWith("/HEAD"))return"";let r=t.startsWith("remotes/")?t.slice(8):t;return r.startsWith("origin/")?r.slice(7):r},Or=async e=>{let t=await oe(e).branch(["-a"]);return new Set(t.all.map(E).filter(Boolean)).size},$r=async(e,t)=>{let r=oe(e),[o,n]=await Promise.all([r.branch(["-a"]),r.branchLocal()]),i="";try{i=E(await r.raw(["symbolic-ref","--quiet","--short","refs/remotes/origin/HEAD"]))}catch{i=""}let s=Array.from(new Set(o.all.map(E).filter(Boolean))).sort((u,p)=>u.localeCompare(p)),a=E(t??""),c=E(n.current),l=[i,a,c,s[0],$].find(u=>u&&(u===$||s.includes(u)))??$;return{branches:s,defaultBranch:l}},dt=async e=>{let t=re(e);try{if(!(await Re(t)).isDirectory())return{ok:!1,path:t,message:"\u8BF7\u9009\u62E9\u76EE\u5F55\u800C\u975E\u6587\u4EF6"}}catch{return{ok:!1,path:t,message:"\u76EE\u5F55\u4E0D\u5B58\u5728"}}let r=_.basename(t);try{let o=oe(t);if(!await o.checkIsRepo())return{ok:!0,path:t,name:r,message:"\u76EE\u5F55\u65E0 Git \u4ED3\u5E93"};let s=(await o.getRemotes(!0)).find(c=>c.name==="origin"),a=await Or(t);return{ok:!0,path:t,name:r,gitUrl:s?.refs.fetch||"",message:a>0?`\u5DF2\u68C0\u6D4B\u5230 Git \u4ED3\u5E93\uFF0C\u5171 ${a} \u4E2A\u5206\u652F`:"\u5DF2\u68C0\u6D4B\u5230 Git \u4ED3\u5E93"}}catch{return{ok:!0,path:t,name:r,message:"\u65E0\u6CD5\u8BFB\u53D6 Git \u4FE1\u606F"}}},lt=async(e,t)=>{let{resolvedRootPath:r,requestedPath:o}=Ar(e,t);if(!Br(r,o))return{ok:!1,path:r,rootPath:r,entries:[],message:"\u53EA\u80FD\u6D4F\u89C8\u8BE5\u5DE5\u4F5C\u7AD9 workspace \u5185\u7684\u76EE\u5F55\u3002"};try{if(!(await Re(o)).isDirectory())return{ok:!1,path:o,rootPath:r,entries:[],message:"\u5F53\u524D\u8DEF\u5F84\u4E0D\u662F\u76EE\u5F55\u3002"}}catch{return{ok:!1,path:o,rootPath:r,entries:[],message:"\u76EE\u5F55\u4E0D\u5B58\u5728\u3002"}}try{let i=(await Dr(o,{withFileTypes:!0})).filter(s=>s.isDirectory()).map(s=>({name:s.name,path:_.join(o,s.name)})).sort((s,a)=>s.name.localeCompare(a.name,"zh-Hans-CN"));return{ok:!0,path:o,rootPath:r,parentPath:o===r?void 0:_.dirname(o),entries:i,message:i.length>0?`\u5171\u627E\u5230 ${i.length} \u4E2A\u6587\u4EF6\u5939\u3002`:"\u5F53\u524D\u76EE\u5F55\u4E0B\u6CA1\u6709\u5B50\u6587\u4EF6\u5939\u3002"}}catch(n){return{ok:!1,path:o,rootPath:r,entries:[],message:n instanceof Error?n.message:"\u8BFB\u53D6\u76EE\u5F55\u5931\u8D25\u3002"}}},ut=async(e,t)=>{let r=re(e);try{if(!(await Re(r)).isDirectory())return{ok:!1,branches:[],defaultBranch:E(t??"")||$,message:`\u9879\u76EE\u76EE\u5F55 ${r} \u4E0D\u662F\u6709\u6548 Git \u4ED3\u5E93\uFF0C\u65E0\u6CD5\u8BFB\u53D6\u5206\u652F\u5217\u8868\u3002`}}catch{return{ok:!1,branches:[],defaultBranch:E(t??"")||$,message:`\u9879\u76EE\u76EE\u5F55 ${r} \u4E0D\u662F\u6709\u6548 Git \u4ED3\u5E93\uFF0C\u65E0\u6CD5\u8BFB\u53D6\u5206\u652F\u5217\u8868\u3002`}}try{if(!await oe(r).checkIsRepo())return{ok:!1,branches:[],defaultBranch:E(t??"")||$,message:`\u9879\u76EE\u76EE\u5F55 ${r} \u4E0D\u662F\u6709\u6548 Git \u4ED3\u5E93\uFF0C\u65E0\u6CD5\u8BFB\u53D6\u5206\u652F\u5217\u8868\u3002`};let i=await $r(r,t);return{ok:!0,branches:i.branches,defaultBranch:i.defaultBranch}}catch(o){return{ok:!1,branches:[],defaultBranch:E(t??"")||$,message:o instanceof Error?o.message:"\u8BFB\u53D6\u5206\u652F\u5217\u8868\u5931\u8D25\u3002"}}};var _r=e=>e.trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^-+|-+$/g,"")||"task",K=e=>`task/${_r(e)}`;var Nr=e=>{let t=e.split(/\r?\n/).map(r=>r.trim()).find(Boolean);return t?t.length>160?`${t.slice(0,157)}...`:t:"Worker completed the task."},Ur=e=>{let t=Nr(e.summary),r=(e.taskTitle?.trim()||e.taskDescription?.trim()||"Worker delivery update").replace(/\s+/g," "),o=e.filesChanged.length>0?e.filesChanged.join(", "):"none";return["## Summary",`- ${r}`,`- ${t}`,`- Changed files: ${o}`,"","## Delivery",`- Base branch: ${e.baseBranch}`,`- Compare branch: ${e.compareBranch}`,e.commitShas?.[0]?`- Commit: ${e.commitShas[0]}`:null].filter(Boolean).join(`
|
|
5
|
+
`)},jr=(e,t)=>{let r=(e?.trim()||t?.trim()||"Worker delivery update").replace(/\s+/g," ");return r.length>72?`${r.slice(0,69)}...`:r},Lr=e=>{let t={mode:e.returnMode};if(e.returnMode==="patch"&&(t.patch=e.patchArtifactId?{artifactId:e.patchArtifactId,files:e.filesChanged,status:"ready"}:{artifactId:void 0,files:e.filesChanged,status:"blocked",reason:e.filesChanged.length===0?"\u672C\u6B21\u6267\u884C\u6CA1\u6709\u6587\u4EF6\u6539\u52A8\uFF0C\u672A\u751F\u6210 patch\u3002":"\u7F3A\u5C11 patch \u4EA7\u7269\uFF0C\u6682\u65F6\u65E0\u6CD5\u540C\u6B65\u5230\u672C\u5730\u4ED3\u5E93\u3002"},t.patch.status==="blocked"&&(t.syncFailureReason=t.patch.reason)),e.returnMode==="branch"||e.returnMode==="commit"){let r=e.remoteBranchName||K(e.taskId),o=e.filesChanged.length>0&&!!e.commitShas?.length,n=!!e.remoteBranchName;t.branch={branchName:r,repoUrl:e.repoUrl,baseBranch:e.baseBranch,pushed:n,suggestedNextStep:o?n?e.returnMode==="commit"?"\u786E\u8BA4 PR \u51C6\u5907\u4FE1\u606F\u540E\u5373\u53EF\u53D1\u8D77 PR\u3002":"\u5230\u76EE\u6807\u4ED3\u5E93\u67E5\u770B\u8BE5\u5206\u652F\uFF0C\u5E76\u51B3\u5B9A\u662F\u5426\u7EE7\u7EED\u53D1\u8D77 PR\u3002":"\u5F53\u524D\u53EA\u6709\u672C\u5730 commit\uFF0C\u8865\u9F50\u51ED\u8BC1\u540E\u63A8\u9001\u8BE5\u5206\u652F\u3002":"\u672C\u6B21\u6267\u884C\u6CA1\u6709\u751F\u6210\u53EF\u4EA4\u4ED8\u5206\u652F\u3002",reason:o?n?void 0:"\u5206\u652F\u540D\u5DF2\u51C6\u5907\uFF0C\u4F46\u5F53\u524D\u7ED3\u679C\u91CC\u8FD8\u6CA1\u6709\u8FDC\u7AEF\u63A8\u9001\u8BB0\u5F55\u3002":e.filesChanged.length===0?"\u672C\u6B21\u6267\u884C\u6CA1\u6709\u4EE3\u7801\u6539\u52A8\uFF0C\u672A\u751F\u6210\u5206\u652F\u4EA4\u4ED8\u3002":"\u672A\u8BB0\u5F55 commit \u7ED3\u679C\uFF0C\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u5206\u652F\u4EA4\u4ED8\u3002"},t.branch.reason&&!o&&(t.syncFailureReason=t.branch.reason)}if(e.returnMode==="commit"){let r=e.remoteBranchName||K(e.taskId),o=e.filesChanged.length>0&&!!e.commitShas?.length;t.pullRequest={ready:o,remoteReady:!!e.remoteBranchName,repoUrl:e.repoUrl,title:o?jr(e.taskTitle,e.taskDescription):void 0,description:o?Ur({summary:e.summary,taskTitle:e.taskTitle,taskDescription:e.taskDescription,filesChanged:e.filesChanged,baseBranch:e.baseBranch,compareBranch:r,commitShas:e.commitShas}):void 0,baseBranch:e.baseBranch,compareBranch:r,reason:o?e.remoteBranchName?void 0:"PR \u6587\u6848\u5DF2\u51C6\u5907\uFF0C\u4F46 compare branch \u8FD8\u6CA1\u6709\u63A8\u9001\u5230\u8FDC\u7AEF\u3002":e.filesChanged.length===0?"\u672C\u6B21\u6267\u884C\u6CA1\u6709\u4EE3\u7801\u6539\u52A8\uFF0C\u672A\u751F\u6210 PR \u51C6\u5907\u4FE1\u606F\u3002":"\u5F53\u524D\u7ED3\u679C\u91CC\u6CA1\u6709 commit \u4FE1\u606F\uFF0C\u6682\u65F6\u65E0\u6CD5\u51C6\u5907 PR\u3002"},t.pullRequest.ready||(t.syncFailureReason=t.pullRequest.reason)}return t},z=(e,t)=>({...e,delivery:Lr({taskId:e.taskId,returnMode:e.returnMode,repoUrl:t.repoUrl,baseBranch:t.baseBranch,summary:e.summary,taskTitle:t.taskTitle,taskDescription:t.taskDescription,filesChanged:e.filesChanged,patchArtifactId:e.patchArtifactId,remoteBranchName:e.remoteBranchName,commitShas:e.commitShas})});import{spawn as no}from"node:child_process";import{createHash as so}from"node:crypto";import{existsSync as io,mkdirSync as yt,rmSync as ao}from"node:fs";import ie from"node:path";import{simpleGit as ae}from"simple-git";import{mkdirSync as Gr,rmSync as Hr,writeFileSync as pt}from"node:fs";import Fr from"node:os";import Ee from"node:path";import{spawnSync as Vr}from"node:child_process";var q=(e,t)=>{let r=Vr("git",t,{cwd:e,encoding:"utf8"});if(r.status!==0)throw new Error((r.stderr||r.stdout||"git command failed").trim())},ne=(e,t)=>{try{q(e,t)}catch{return}},mt=e=>{let t=Ee.join(Fr.tmpdir(),"vibemux-git",e.taskId);Gr(t,{recursive:!0}),e.identity.name&&e.identity.email&&(q(e.worktreePath,["config","--local","user.name",e.identity.name]),q(e.worktreePath,["config","--local","user.email",e.identity.email]));let r=e.httpsToken||e.identity.credentialToken;if(e.identity.authMode==="ssh"&&e.identity.credentialToken){let o=Ee.join(t,"id_ed25519");pt(o,e.identity.credentialToken,{encoding:"utf8",mode:384}),q(e.worktreePath,["config","--local","core.sshCommand",`ssh -i "${o}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null`])}else if(r){let o=Ee.join(t,"credentials");pt(o,`${r}
|
|
6
|
+
`,"utf8"),q(e.worktreePath,["config","--local","credential.helper",`store --file=${o}`])}return{tempDir:t,cleanup(){e.identity.name&&e.identity.email&&(ne(e.worktreePath,["config","--local","--unset-all","user.name"]),ne(e.worktreePath,["config","--local","--unset-all","user.email"])),ne(e.worktreePath,["config","--local","--unset-all","credential.helper"]),ne(e.worktreePath,["config","--local","--unset-all","core.sshCommand"]),Hr(t,{recursive:!0,force:!0})}}};import{existsSync as Kr}from"node:fs";import{createOpencode as zr,createOpencodeClient as qr}from"@opencode-ai/sdk";var ft=e=>{let t=e?.trim();if(!t)return{};try{let r=JSON.parse(t);return r&&typeof r=="object"?r:{}}catch(r){throw new Error(r instanceof Error?`OpenCode \u914D\u7F6E JSON \u65E0\u6548\uFF1A${r.message}`:"OpenCode \u914D\u7F6E JSON \u65E0\u6548")}};var L=null,Pe="",se=null,Jr=e=>{if(!e)return;let[t,...r]=e.split("/"),o=r.join("/");if(!(!t||!o))return{providerID:t,modelID:o}},Qr=async e=>{try{return(await zr({port:4096,timeout:15e3,config:ft(e)})).server}catch{return{url:"http://127.0.0.1:4096",close:()=>{}}}},Xr=async()=>{let e=m().opencodeConfigContent?.trim()??"";return L&&Pe!==e&&(se?.close(),se=null,L=null),L||(Pe=e,L=Qr(e).then(t=>(se=t,t)).catch(t=>{throw L=null,Pe="",se=null,t})),L},Yr=async e=>{let t=await Xr();return qr({baseUrl:t.url,directory:e})},Zr=async(e,t,r)=>{let o=await e.session.create({body:{title:r},query:{directory:t}});if(!o.data?.id)throw new Error("OpenCode \u4F1A\u8BDD\u521B\u5EFA\u5931\u8D25");return o.data.id},eo=async e=>{if(!Kr(e.cwd))throw new Error(`\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u5B58\u5728: ${e.cwd}`);let t=await Yr(e.cwd),r=await Zr(t,e.cwd,e.title),o=!1,n=()=>{o=!0,t.session.abort({path:{id:r},query:{directory:e.cwd}}).catch(()=>{})};e.signal?.addEventListener("abort",n,{once:!0});try{let i=await t.session.prompt({path:{id:r},query:{directory:e.cwd},body:{model:Jr(e.executionModel),parts:[{type:"text",text:e.prompt}]}});if(o||e.signal?.aborted)throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");if(!i.data)throw new Error("OpenCode \u8C03\u7528\u5931\u8D25");let s=i.data.parts.filter(a=>a.type==="text").map(a=>a.text?.trim()??"").filter(Boolean).join(`
|
|
7
|
+
|
|
8
|
+
`);return{sessionId:r,output:s||"OpenCode \u672A\u8FD4\u56DE\u6587\u672C\u8F93\u51FA\u3002"}}finally{e.signal?.removeEventListener("abort",n)}},gt=async e=>{let t=[`\u4EFB\u52A1\u6807\u9898: ${e.title}`,`\u4EFB\u52A1\u63CF\u8FF0: ${e.description}`,"","\u8BF7\u76F4\u63A5\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u5B8C\u6210\u4EFB\u52A1\u6240\u9700\u4FEE\u6539\u3002","\u5982\u679C\u9700\u8981\u8BFB\u53D6\u4EE3\u7801\u3001\u7F16\u8F91\u6587\u4EF6\u3001\u8FD0\u884C\u6D4B\u8BD5\u6216\u751F\u6210\u8865\u4E01\uFF0C\u8BF7\u76F4\u63A5\u6267\u884C\u3002","\u5B8C\u6210\u540E\u8BF7\u8FD4\u56DE\u7B80\u6D01\u603B\u7ED3\uFF1A\u505A\u4E86\u4EC0\u4E48\u3001\u662F\u5426\u8FD8\u6709\u963B\u585E\u3002"].join(`
|
|
9
|
+
`);return eo({cwd:e.cwd,title:`Task: ${e.title}`,prompt:t,executionModel:e.executionModel,signal:e.signal})};import{simpleGit as to}from"simple-git";var ro=async e=>{let t=await e.status();return Array.from(new Set(t.files.map(r=>r.path))).sort()},oo=async(e,t)=>{if(t.returnMode!=="patch")return;await e.add(["--all"]);let r=await e.diff(["--cached","--binary",t.baseCommit]);if(r.trim())return{filename:`${t.id}.patch`,content:r}},kt=async(e,t,r)=>{let o=to(t),n=await ro(o),i=await oo(o,e);return{filesChanged:n,commitShas:void 0,patchArtifact:i,remoteBranchName:e.returnMode==="branch"?r:void 0}};var co="main",lo=["&&","||","|",";",">","<","$(","`"],uo=e=>so("sha1").update(e).digest("hex").slice(0,12),po=e=>e.replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"")||"repo",mo=(e,t)=>{let r=ie.basename(t).replace(/\.git$/i,"");return ie.join(e,"repos",`${po(r)}-${uo(t)}`)},fo=e=>e.baseCommit?.trim()||e.defaultBranch?.trim()||co,We=e=>`origin/${e}`,wt=async(e,t)=>(await e.branch(["-r"])).all.includes(We(t)),go=async(e,t)=>{await e.fetch(["--all","--prune"]),await wt(e,t)&&await e.rebase([We(t)])},ko=e=>e.gitIdentity??{mode:e.gitIdentityMode??"personal"},ho=(e,t=1600)=>{let r=e.trim();return r?r.length>t?`${r.slice(0,t)}
|
|
10
|
+
...\uFF08\u5DF2\u622A\u65AD\uFF09`:r:""},yo=e=>{let t=[],r="",o=null,n=!1;for(let i of e){if(n){r+=i,n=!1;continue}if(i==="\\"){n=!0;continue}if(o){i===o?o=null:r+=i;continue}if(i==='"'||i==="'"){o=i;continue}if(/\s/.test(i)){r&&(t.push(r),r="");continue}r+=i}if(n&&(r+="\\"),o)throw new Error("\u547D\u4EE4\u9884\u8BBE\u5B58\u5728\u672A\u95ED\u5408\u7684\u5F15\u53F7\u3002");return r&&t.push(r),t},wo=e=>{let t=e.trim();if(!t)throw new Error("\u547D\u4EE4\u9884\u8BBE\u4E0D\u80FD\u4E3A\u7A7A\u3002");if(lo.some(i=>t.includes(i)))throw new Error("\u547D\u4EE4\u9884\u8BBE\u53EA\u652F\u6301\u5355\u6761\u547D\u4EE4\uFF0C\u4E0D\u652F\u6301 shell \u7BA1\u9053\u3001\u91CD\u5B9A\u5411\u6216\u4E32\u8054\u64CD\u4F5C\u3002");let r=yo(t),o={};for(;r.length>0&&/^[A-Za-z_][A-Za-z0-9_]*=.*/.test(r[0]);){let i=r.shift(),s=i.indexOf("=");o[i.slice(0,s)]=i.slice(s+1)}let n=r.shift();if(!n)throw new Error("\u547D\u4EE4\u9884\u8BBE\u7F3A\u5C11\u53EF\u6267\u884C\u547D\u4EE4\u3002");return{command:n,args:r,env:o}},xo=(e,t)=>{if(e)return t==="install"?e.installCommand?.trim()||void 0:t==="build"?e.buildCommand?.trim()||void 0:t==="test"?e.testCommand?.trim()||void 0:e.lintCommand?.trim()||void 0},bo=e=>e?[e.installCommand?`install: ${e.installCommand}`:null,e.buildCommand?`build: ${e.buildCommand}`:null,e.testCommand?`test: ${e.testCommand}`:null,e.lintCommand?`lint: ${e.lintCommand}`:null,e.branchNamePattern?`branch: ${e.branchNamePattern}`:null].filter(t=>!!t):[],So=e=>{let t=bo(e.commandPreset);return t.length===0?e.description:[e.description,"","\u9879\u76EE\u547D\u4EE4\u9884\u8BBE\uFF1A",...t.map(r=>`- ${r}`),"","\u6267\u884C\u8981\u6C42\uFF1A","- \u4F18\u5148\u9075\u5B88\u4E0A\u9762\u7684\u9879\u76EE\u547D\u4EE4\u9884\u8BBE\u3002","- install \u547D\u4EE4\u7531 worker \u5728\u6267\u884C\u524D\u81EA\u52A8\u8FD0\u884C\u3002","- build / test / lint \u547D\u4EE4\u7531 worker \u5728\u6267\u884C\u540E\u81EA\u52A8\u6821\u9A8C\u3002"].join(`
|
|
11
|
+
`)},To=async e=>{let t=wo(e.commandText);return await new Promise((r,o)=>{let n=no(t.command,t.args,{cwd:e.cwd,env:{...process.env,...t.env},stdio:["ignore","pipe","pipe"]}),i="",s="",a=!1,c=()=>{a=!0,n.kill("SIGTERM")};e.signal?.addEventListener("abort",c,{once:!0}),n.stdout.on("data",l=>{i+=l.toString()}),n.stderr.on("data",l=>{s+=l.toString()}),n.on("error",l=>{e.signal?.removeEventListener("abort",c),o(new Error(`${e.label} \u542F\u52A8\u5931\u8D25\uFF1A${l.message}`))}),n.on("close",(l,u)=>{if(e.signal?.removeEventListener("abort",c),a){o(new Error(`${e.label} \u5DF2\u53D6\u6D88\u3002`));return}let p=ho([i,s].filter(Boolean).join(`
|
|
12
|
+
`));if(l===0){r({output:p});return}let x=u?`\u4FE1\u53F7 ${u}`:`\u9000\u51FA\u7801 ${l??"unknown"}`;o(new Error([`${e.label} \u6267\u884C\u5931\u8D25\uFF08${x}\uFF09\u3002`,p].filter(Boolean).join(`
|
|
13
|
+
|
|
14
|
+
`)))})})},ht=async e=>{let t=xo(e.task.commandPreset,e.step);if(!t)return null;let r=`\u9879\u76EE\u9884\u8BBE ${e.step}`;e.emit("executing",`${r}\uFF1A${t}`);let o=await To({cwd:e.cwd,label:r,commandText:t,signal:e.signal});return e.emit("executing",o.output?`${r} \u5DF2\u5B8C\u6210\u3002
|
|
15
|
+
${o.output}`:`${r} \u5DF2\u5B8C\u6210\u3002`),`${r}\uFF1A${t}`},Co=(e,t)=>e?.find(r=>!!(r.projectId&&r.projectId===t.projectId||r.repoUrl&&r.repoUrl===t.repoUrl))?.localPath,Io=async(e,t,r)=>{let o=Co(r,t)||mo(e,t.repoUrl);yt(ie.dirname(o),{recursive:!0}),io(o)||await ae().clone(t.repoUrl,o);let n=ae(o);return await n.fetch(["--all","--prune"]),{repoDir:o,git:n}},Ro=async(e,t)=>{let r=ae(e);try{await r.raw(["worktree","remove","--force",t])}catch{}ao(t,{recursive:!0,force:!0}),yt(ie.dirname(t),{recursive:!0})},Eo=async e=>{let t=ae(e.worktreePath),r=await t.status(),o=Array.from(new Set(r.files.map(a=>a.path))).sort();if(o.length===0)return{changedFiles:o,commitShas:void 0,remoteBranchName:void 0,pushMessage:"\u6CA1\u6709\u6587\u4EF6\u6539\u52A8\uFF0C\u8DF3\u8FC7\u63D0\u4EA4\u4E0E\u63A8\u9001\u3002"};if(e.task.returnMode!=="branch"&&e.task.returnMode!=="commit")return{changedFiles:o,commitShas:void 0,remoteBranchName:void 0,pushMessage:void 0};if(!e.identity.name||!e.identity.email)throw new Error("\u5F53\u524D\u4EFB\u52A1\u7F3A\u5C11 Git \u7528\u6237\u540D\u6216\u90AE\u7BB1\uFF0C\u8BF7\u5148\u5728\u8BBE\u7F6E\u9875\u5B8C\u6210 Git \u6388\u6743\u914D\u7F6E\u3002");await t.add(["--all"]),await t.commit(`vibemux: ${e.task.id}`);let n=(await t.revparse(["HEAD"])).trim(),i,s="\u5DF2\u521B\u5EFA\u672C\u5730\u63D0\u4EA4\u3002";return e.identity.credentialToken?(await go(t,e.branchName),await t.push(["-u","origin",e.branchName]),i=e.branchName,s=`\u5DF2\u63A8\u9001\u8FDC\u7AEF\u5206\u652F ${e.branchName}\u3002`):(e.task.returnMode==="branch"||e.task.returnMode==="commit")&&(s="\u7F3A\u5C11 task \u7EA7\u4E34\u65F6\u51ED\u8BC1\uFF0C\u5DF2\u521B\u5EFA\u672C\u5730\u63D0\u4EA4\u4F46\u672A\u63A8\u9001\u8FDC\u7AEF\u3002"),{changedFiles:o,commitShas:n?[n]:void 0,remoteBranchName:i,pushMessage:s}},xt=async e=>{let t=new Date().toISOString(),r=K(e.task.id),o=it(e.workspaceRoot,e.task.id),n=null;try{e.emit("preparing","\u68C0\u67E5 Worker \u8FD0\u884C\u73AF\u5883");let i=await v({autoInstall:!0});if(!i.ok)throw new Error(i.message);e.emit("preparing",i.changed?"\u8FD0\u884C\u73AF\u5883\u5DF2\u81EA\u52A8\u8865\u9F50":"\u8FD0\u884C\u73AF\u5883\u5DF2\u5C31\u7EEA"),e.emit("preparing","\u51C6\u5907\u672C\u5730\u4ED3\u5E93");let{repoDir:s,git:a}=await Io(e.workspaceRoot,e.task,e.projectBindings);e.emit("preparing",`\u4ED3\u5E93\u5DF2\u5C31\u7EEA\uFF1A${s}`),await Ro(s,o);let c=await wt(a,r)?We(r):fo(e.task);await a.raw(["worktree","add","--force","-B",r,o,c]);let l=ko(e.task);n=mt({taskId:e.task.id,worktreePath:o,identity:l}),e.emit("executing",`\u5DF2\u521B\u5EFA worktree\uFF1A${o}`);let u=[],p=await ht({step:"install",task:e.task,cwd:o,signal:e.signal,emit:e.emit});p&&u.push(p),e.emit("executing","\u5F00\u59CB\u8C03\u7528 OpenCode \u6267\u884C\u4EFB\u52A1");let x=await gt({cwd:o,title:`Distributed Task ${e.task.id}`,description:So(e.task),signal:e.signal});for(let S of["build","test","lint"]){let _e=await ht({step:S,task:e.task,cwd:o,signal:e.signal,emit:e.emit});_e&&u.push(_e)}let d=await Eo({task:e.task,worktreePath:o,branchName:r,identity:l}),f=await kt(e.task,o,r),h;f.patchArtifact&&e.executorToken&&(e.emit("syncing_back","\u6B63\u5728\u4E0A\u4F20 patch \u4EA7\u7269\u5230\u63A7\u5236\u9762"),h=(await Je({cloudUrl:e.cloudUrl,executorToken:e.executorToken,taskId:e.task.id,filename:f.patchArtifact.filename,content:f.patchArtifact.content})).artifactId);let b=new Date().toISOString(),k=z({taskId:e.task.id,status:"completed",returnMode:e.task.returnMode,summary:[u.length>0?`\u5DF2\u6267\u884C\u9879\u76EE\u9884\u8BBE\u547D\u4EE4\uFF1A
|
|
16
|
+
${u.map(S=>`- ${S}`).join(`
|
|
17
|
+
`)}`:void 0,x.output,d.pushMessage].filter(Boolean).join(`
|
|
18
|
+
|
|
19
|
+
`),filesChanged:d.changedFiles.length>0?d.changedFiles:f.filesChanged,remoteBranchName:d.remoteBranchName,commitShas:d.commitShas??f.commitShas,patchArtifactId:h,startedAt:t,completedAt:b,durationSec:Math.max(0,Math.round((new Date(b).getTime()-new Date(t).getTime())/1e3)),executorNodeId:e.executorId,opencodeSessionId:x.sessionId},{repoUrl:e.task.repoUrl,baseBranch:e.task.defaultBranch,taskDescription:e.task.description});return{task:{...e.task,status:"completed",startedAt:t,completedAt:b,updatedAt:b,result:k}}}catch(i){let s=new Date().toISOString(),a=i instanceof Error?i.message:"worker task execution failed";return{task:{...e.task,status:"failed",startedAt:t,completedAt:s,updatedAt:s,errorMessage:a,result:z({taskId:e.task.id,status:"failed",returnMode:e.task.returnMode,summary:a,filesChanged:[],startedAt:t,completedAt:s,durationSec:Math.max(0,Math.round((new Date(s).getTime()-new Date(t).getTime())/1e3)),executorNodeId:e.executorId},{repoUrl:e.task.repoUrl,baseBranch:e.task.defaultBranch,taskDescription:e.task.description})}}}finally{n?.cleanup()}};var ve=new Map,De=e=>{let t=ve.get(e);if(t){for(let r of t)clearTimeout(r);ve.delete(e)}},bt=(e,t,r)=>{let o=new Date().toISOString();return{...e,status:"cancelled",executorNodeId:t,completedAt:o,updatedAt:o,result:z({taskId:e.id,status:"cancelled",returnMode:e.returnMode,summary:r??"\u4EFB\u52A1\u5DF2\u53D6\u6D88",filesChanged:[],startedAt:e.startedAt??o,completedAt:o,durationSec:0,executorNodeId:t},{repoUrl:e.repoUrl,baseBranch:e.defaultBranch,taskDescription:e.description})}},St=De,Tt=e=>{let{task:t,executorId:r,workspaceRoot:o,projectBindings:n,cloudUrl:i,executorToken:s,connection:a}=e,c=new AbortController,l=[setTimeout(()=>{e.onStart(t.id),xt({task:t,executorId:r,workspaceRoot:o,projectBindings:n,signal:c.signal,cloudUrl:i,executorToken:s,emit(u,p){c.signal.aborted||a.send({type:"task.event",taskId:t.id,idempotencyKey:t.idempotencyKey,executorId:r,status:u,message:p,at:new Date().toISOString()})}}).then(({task:u})=>{c.signal.aborted||(e.onFinish(t.id),a.send({type:"task.result",executorId:r,task:u}),De(t.id))})},20)];return ve.set(t.id,l),{abort(){c.abort(),e.onFinish(t.id),De(t.id)}}};var N=(e,t)=>{console.log("[terminal][worker]",e,t??{})},Po=e=>{let{connection:t,executorId:r,sessionId:o,stream:n,chunk:i}=e;t.send({type:"executor.terminal.session.output",executorId:r,output:{sessionId:o,stream:n,chunk:i,at:new Date().toISOString()}})},Ct=e=>{let t=m(),r={...t,opencodeConfigContent:e.opencodeConfigContent??"",defaultModel:e.defaultModel??"",maxConcurrency:Math.max(1,e.maxConcurrency??t.maxConcurrency)};return G(r),y({config:r}),r},It=e=>t=>{if(e.getCurrentSocket()!==e.expectedSocket)return;let r=e.getConfig(),o=e.getConnection();if(t.type==="control-plane.ready"){r=Ct({opencodeConfigContent:t.opencodeConfigContent,defaultModel:t.defaultModel,maxConcurrency:t.maxConcurrency}),e.setConfig(r),y({config:r,executorId:r.executorId});return}if(t.type==="config.sync"){r=Ct({opencodeConfigContent:t.opencodeConfigContent,defaultModel:t.defaultModel,maxConcurrency:t.maxConcurrency}),e.setConfig(r),y({config:r});return}if(t.type==="config.export.request"){r=m(),e.setConfig(r),o?.send({type:"config.export.response",executorId:r.executorId,requestId:t.requestId,opencodeConfigContent:r.opencodeConfigContent,defaultModel:r.defaultModel,at:new Date().toISOString()});return}if(t.type==="executor.repo-probe.request"){r=m(),e.setConfig(r),dt(t.localPath).then(n=>{o?.send({type:"executor.repo-probe.response",executorId:r.executorId,requestId:t.requestId,result:n,at:new Date().toISOString()})});return}if(t.type==="executor.directory-browse.request"){r=m(),e.setConfig(r),lt(t.rootPath||r.workspaceRoot,t.directoryPath).then(n=>{o?.send({type:"executor.directory-browse.response",executorId:r.executorId,requestId:t.requestId,result:n,at:new Date().toISOString()})});return}if(t.type==="executor.repo-branches.request"){r=m(),e.setConfig(r),ut(t.localPath,t.preferredBranch).then(n=>{o?.send({type:"executor.repo-branches.response",executorId:r.executorId,requestId:t.requestId,result:n,at:new Date().toISOString()})});return}if(t.type==="executor.terminal.request"){r=m(),e.setConfig(r),N("received one-shot terminal request",{requestId:t.requestId,command:t.command,cwd:t.cwd||r.workspaceRoot}),e.runTerminalCommand(t.command,t.cwd||r.workspaceRoot).then(n=>{o?.send({type:"executor.terminal.response",executorId:r.executorId,requestId:t.requestId,result:n})});return}if(t.type==="executor.terminal.session.open"){r=m(),e.setConfig(r);let n=t.cwd||r.workspaceRoot;N("received terminal session open",{sessionId:t.sessionId,cwd:n});let i=e.openTerminalSession({cwd:n,onExit:s=>{e.terminalSessions.delete(t.sessionId),o?.send({type:"executor.terminal.session.exit",executorId:r.executorId,sessionId:t.sessionId,exitCode:s,at:new Date().toISOString()})},onLog:(s,a)=>{N(s,{sessionId:t.sessionId,...a})},onOutput:(s,a)=>{Po({connection:o,executorId:r.executorId,sessionId:t.sessionId,stream:s,chunk:a})},onReady:(s,a)=>{o?.send({type:"executor.terminal.session.ready",executorId:r.executorId,sessionId:t.sessionId,cwd:n,mode:s,at:new Date().toISOString()}),N("terminal session ready",{sessionId:t.sessionId,cwd:n,mode:s,backend:a})}});e.terminalSessions.set(t.sessionId,i);return}if(t.type==="executor.terminal.session.input"){N("received terminal session input",{sessionId:t.sessionId,inputLength:t.input.length,preview:t.input.slice(0,80)}),e.terminalSessions.get(t.sessionId)?.write(t.input);return}if(t.type==="executor.terminal.session.resize"){N("received terminal session resize",{sessionId:t.sessionId,cols:t.cols,rows:t.rows}),e.terminalSessions.get(t.sessionId)?.resize(t.cols,t.rows);return}if(t.type==="executor.terminal.session.close"){N("received terminal session close",{sessionId:t.sessionId});let n=e.terminalSessions.get(t.sessionId);n&&(n.kill(),e.terminalSessions.delete(t.sessionId));return}if(t.type==="task.assign"){let n=e.getRunningTaskIds(),i=e.getQueuedTaskIds();if(e.assignedTasks.has(t.task.id)||n.includes(t.task.id)||i.includes(t.task.id)){e.assignedTasks.set(t.task.id,t.task);return}e.assignedTasks.set(t.task.id,t.task),e.setQueuedTaskIds([...i,t.task.id]),e.syncRuntimeState(),o?.send({type:"task.ack",taskId:t.task.id,idempotencyKey:t.task.idempotencyKey,executorId:r.executorId,accepted:!0}),e.drainAssignedQueue();return}if(t.type==="task.cancel"){let n=e.assignedTasks.get(t.taskId);e.activeExecutions.get(t.taskId)?.abort(),e.activeExecutions.delete(t.taskId),St(t.taskId),e.assignedTasks.delete(t.taskId),e.setQueuedTaskIds(e.getQueuedTaskIds().filter(i=>i!==t.taskId)),e.setRunningTaskIds(e.getRunningTaskIds().filter(i=>i!==t.taskId)),e.syncRuntimeState(),e.drainAssignedQueue(),n&&o?.send({type:"task.result",executorId:r.executorId,task:bt(n,r.executorId,t.reason)})}};import{existsSync as Me,mkdirSync as Wo,readFileSync as Rt}from"node:fs";import{spawnSync as vo}from"node:child_process";import Do from"node:os";import ce from"node:path";var Et=ce.join(Do.homedir(),".ssh"),Mo=["id_ed25519.pub","id_rsa.pub","id_ecdsa.pub"],Bo="vibemux_worker_ed25519",Ao=e=>{Wo(ce.dirname(e),{recursive:!0});let t=vo("ssh-keygen",["-t","ed25519","-C","vibemux-worker","-f",e,"-N",""],{encoding:"utf8"});if(t.status!==0)throw new Error((t.stderr||t.stdout||"ssh-keygen \u6267\u884C\u5931\u8D25\u3002").trim())},Be=()=>{try{for(let r of Mo){let o=ce.join(Et,r);if(Me(o))return Rt(o,"utf8").trim()}let e=ce.join(Et,Bo),t=`${e}.pub`;return Me(t)||Ao(e),Me(t)?Rt(t,"utf8").trim():void 0}catch{return}};import{spawn as Ae,spawnSync as Oo}from"node:child_process";import{accessSync as $o,constants as _o}from"node:fs";import{StringDecoder as Pt}from"node:string_decoder";import No from"node-pty";var vt=120,Dt=32,Oe="xterm-256color",Uo=250,jo=String.raw`
|
|
20
|
+
import base64
|
|
21
|
+
import errno
|
|
22
|
+
import fcntl
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import pty
|
|
26
|
+
import select
|
|
27
|
+
import signal
|
|
28
|
+
import struct
|
|
29
|
+
import sys
|
|
30
|
+
import termios
|
|
31
|
+
import threading
|
|
32
|
+
|
|
33
|
+
shell = os.environ.get('DEVKANBAN_SHELL') or '/bin/sh'
|
|
34
|
+
cwd = os.environ.get('DEVKANBAN_CWD') or os.getcwd()
|
|
35
|
+
cols = max(1, int(os.environ.get('DEVKANBAN_COLS') or '120'))
|
|
36
|
+
rows = max(1, int(os.environ.get('DEVKANBAN_ROWS') or '32'))
|
|
37
|
+
term_name = os.environ.get('DEVKANBAN_TERM_NAME') or 'xterm-256color'
|
|
38
|
+
|
|
39
|
+
def set_winsize(fd, cols, rows):
|
|
40
|
+
fcntl.ioctl(fd, termios.TIOCSWINSZ, struct.pack('HHHH', rows, cols, 0, 0))
|
|
41
|
+
|
|
42
|
+
pid, master_fd = pty.fork()
|
|
43
|
+
|
|
44
|
+
if pid == 0:
|
|
45
|
+
try:
|
|
46
|
+
os.chdir(cwd)
|
|
47
|
+
except OSError as error:
|
|
48
|
+
sys.stderr.write(f'[python-pty] chdir failed: {error}\n')
|
|
49
|
+
sys.stderr.flush()
|
|
50
|
+
os._exit(1)
|
|
51
|
+
|
|
52
|
+
os.environ['TERM'] = term_name
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
os.execvpe(shell, [shell, '-i', '-l'], os.environ)
|
|
56
|
+
except OSError as error:
|
|
57
|
+
sys.stderr.write(f'[python-pty] exec failed: {error}\n')
|
|
58
|
+
sys.stderr.flush()
|
|
59
|
+
os._exit(1)
|
|
60
|
+
|
|
61
|
+
set_winsize(master_fd, cols, rows)
|
|
62
|
+
closed = False
|
|
63
|
+
|
|
64
|
+
def terminate_child():
|
|
65
|
+
global closed
|
|
66
|
+
if closed:
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
closed = True
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
os.kill(pid, signal.SIGTERM)
|
|
73
|
+
except ProcessLookupError:
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
def handle_control_message(message):
|
|
77
|
+
msg_type = message.get('type')
|
|
78
|
+
|
|
79
|
+
if msg_type == 'input':
|
|
80
|
+
data = base64.b64decode(message.get('data') or '')
|
|
81
|
+
if data:
|
|
82
|
+
os.write(master_fd, data)
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
if msg_type == 'resize':
|
|
86
|
+
next_cols = max(1, int(message.get('cols') or cols))
|
|
87
|
+
next_rows = max(1, int(message.get('rows') or rows))
|
|
88
|
+
set_winsize(master_fd, next_cols, next_rows)
|
|
89
|
+
try:
|
|
90
|
+
os.kill(pid, signal.SIGWINCH)
|
|
91
|
+
except ProcessLookupError:
|
|
92
|
+
pass
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
if msg_type == 'close':
|
|
96
|
+
terminate_child()
|
|
97
|
+
|
|
98
|
+
def control_loop():
|
|
99
|
+
try:
|
|
100
|
+
for line in sys.stdin:
|
|
101
|
+
text = line.strip()
|
|
102
|
+
if not text:
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
handle_control_message(json.loads(text))
|
|
107
|
+
except Exception as error:
|
|
108
|
+
sys.stderr.write(f'[python-pty] control error: {error}\n')
|
|
109
|
+
sys.stderr.flush()
|
|
110
|
+
finally:
|
|
111
|
+
terminate_child()
|
|
112
|
+
|
|
113
|
+
threading.Thread(target=control_loop, daemon=True).start()
|
|
114
|
+
|
|
115
|
+
exit_code = 0
|
|
116
|
+
|
|
117
|
+
while True:
|
|
118
|
+
try:
|
|
119
|
+
readable, _, _ = select.select([master_fd], [], [], 0.2)
|
|
120
|
+
except (OSError, ValueError):
|
|
121
|
+
break
|
|
122
|
+
|
|
123
|
+
if master_fd in readable:
|
|
124
|
+
try:
|
|
125
|
+
chunk = os.read(master_fd, 65536)
|
|
126
|
+
except OSError as error:
|
|
127
|
+
if error.errno == errno.EIO:
|
|
128
|
+
break
|
|
129
|
+
raise
|
|
130
|
+
|
|
131
|
+
if not chunk:
|
|
132
|
+
break
|
|
133
|
+
|
|
134
|
+
os.write(sys.stdout.fileno(), chunk)
|
|
135
|
+
sys.stdout.flush()
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
result = os.waitpid(pid, os.WNOHANG)
|
|
139
|
+
except ChildProcessError:
|
|
140
|
+
break
|
|
141
|
+
|
|
142
|
+
if result != (0, 0):
|
|
143
|
+
status = result[1]
|
|
144
|
+
|
|
145
|
+
if os.WIFEXITED(status):
|
|
146
|
+
exit_code = os.WEXITSTATUS(status)
|
|
147
|
+
elif os.WIFSIGNALED(status):
|
|
148
|
+
exit_code = 128 + os.WTERMSIG(status)
|
|
149
|
+
|
|
150
|
+
break
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
os.close(master_fd)
|
|
154
|
+
except OSError:
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
sys.exit(exit_code)
|
|
158
|
+
`,D,Lo=["-i","-l"],de=e=>{if(!e?.trim())return!1;try{return $o(e,_o.X_OK),!0}catch{return!1}},Wt=e=>{let t=e.end();return t.length>0?t:""},$e=(e,t,r)=>{let o=!1,n=setTimeout(()=>{o||(o=!0,e(t,r))},Uo);return{markReady:()=>{o||(o=!0,clearTimeout(n),e(t,r))},dispose:()=>{clearTimeout(n)}}},Go=()=>{if(D!==void 0)return D;let e=["/usr/bin/python3","/opt/homebrew/bin/python3"];for(let r of e)if(de(r))return D=r,D;let t=["python3","python"];for(let r of t){let o=Oo(r,["-c","import sys"],{stdio:"ignore"});if(!o.error&&o.status===0)return D=r,D}return D=null,D},le=()=>{let e=process.env.SHELL?.trim();return e&&!e.includes("fish")&&de(e)?e:process.platform==="darwin"&&de("/bin/zsh")?"/bin/zsh":de("/bin/bash")?"/bin/bash":"sh"},Mt=(e,t)=>new Promise(r=>{let o=le(),n=Ae(o,["-l","-c",e],{cwd:t?.trim()||process.cwd(),env:process.env}),i="",s="";n.stdout.on("data",a=>{i+=a.toString()}),n.stderr.on("data",a=>{s+=a.toString()}),n.on("close",a=>{r({command:e,cwd:t,stdout:i,stderr:s,exitCode:a??0,at:new Date().toISOString()})}),n.on("error",a=>{r({command:e,cwd:t,stdout:i,stderr:`${s}${a.message}`,exitCode:1,at:new Date().toISOString()})})}),Ho=e=>{let{cols:t=vt,cwd:r,onExit:o,onLog:n,onOutput:i,onReady:s,rows:a=Dt,shell:c=le()}=e,l=No.spawn(c,Lo,{cwd:r,env:process.env,cols:t,rows:a,name:Oe}),u=$e(s,"pty","node-pty");return l.onData(p=>{u.markReady(),n("pty output",{backend:"node-pty",chunkLength:p.length,preview:p.slice(0,120)}),i("stdout",p)}),l.onExit(({exitCode:p})=>{u.dispose(),n("pty exit",{backend:"node-pty",exitCode:p}),o(p??0)}),{backend:"node-pty",mode:"pty",write:p=>l.write(p),resize:(p,x)=>l.resize(Math.max(1,p),Math.max(1,x)),kill:()=>l.kill("SIGTERM")}},Fo=e=>{let{cols:t=vt,cwd:r,onExit:o,onLog:n,onOutput:i,onReady:s,rows:a=Dt,shell:c=le()}=e,l=Go();if(!l)throw new Error("python3 \u4E0D\u53EF\u7528\uFF0C\u65E0\u6CD5\u542F\u52A8 PTY fallback\u3002");let u=Ae(l,["-u","-c",jo],{cwd:r,env:{...process.env,DEVKANBAN_COLS:String(Math.max(1,t)),DEVKANBAN_CWD:r,DEVKANBAN_ROWS:String(Math.max(1,a)),DEVKANBAN_SHELL:c,DEVKANBAN_TERM_NAME:Oe},stdio:["pipe","pipe","pipe"]}),p=u.stdin;if(!p)throw u.kill("SIGTERM"),new Error("python PTY control pipe \u4E0D\u53EF\u7528\u3002");let x=new Pt("utf8"),d=new Pt("utf8"),f=$e(s,"pty","python-pty"),h=(k,S)=>{S.length!==0&&(k==="stdout"&&f.markReady(),n("pty output",{backend:"python-pty",chunkLength:S.length,preview:S.slice(0,120),stream:k}),i(k,S))};u.stdout.on("data",k=>{h("stdout",x.write(k))}),u.stderr.on("data",k=>{h("system",d.write(k))}),u.on("error",k=>{n("python pty error",{message:k.message}),i("system",`\r
|
|
159
|
+
[python-pty error] ${k.message}\r
|
|
160
|
+
`)}),u.on("close",k=>{f.dispose(),h("stdout",Wt(x)),h("system",Wt(d)),n("pty exit",{backend:"python-pty",exitCode:k??0}),o(k??0)});let b=k=>{p.destroyed||p.write(`${JSON.stringify(k)}
|
|
161
|
+
`)};return{backend:"python-pty",mode:"pty",write:k=>{b({type:"input",data:Buffer.from(k,"utf8").toString("base64")})},resize:(k,S)=>{b({type:"resize",cols:Math.max(1,k),rows:Math.max(1,S)})},kill:()=>{b({type:"close"}),p.end(),u.kill("SIGTERM")}}},Vo=e=>{let{cwd:t,onExit:r,onLog:o,onOutput:n,onReady:i}=e,s=Ae("/bin/bash",["-il"],{cwd:t,env:{...process.env,BASH_SILENCE_DEPRECATION_WARNING:"1",TERM:process.env.TERM||Oe},stdio:"pipe"});s.stdin.setDefaultEncoding("utf8"),s.stdout.setEncoding("utf8"),s.stderr.setEncoding("utf8");let a=$e(i,"pipe","pipe");return s.stdout.on("data",c=>{a.markReady(),o("fallback shell stdout",{backend:"pipe",chunkLength:c.length,preview:c.slice(0,120)}),n("stdout",c)}),s.stderr.on("data",c=>{a.markReady(),o("fallback shell stderr",{backend:"pipe",chunkLength:c.length,preview:c.slice(0,120)}),n("stderr",c)}),s.on("error",c=>{o("fallback shell error",{message:c.message}),n("system",`\r
|
|
162
|
+
[fallback shell error] ${c.message}\r
|
|
163
|
+
`)}),s.on("close",c=>{a.dispose(),o("fallback shell exit",{backend:"pipe",exitCode:c??0}),r(c??0)}),s.stdin.write(`export PS1="$ "
|
|
164
|
+
`),{backend:"pipe",mode:"pipe",write:c=>{s.stdin.destroyed||s.stdin.write(c.replace(/\r/g,`
|
|
165
|
+
`))},resize:()=>{},kill:()=>s.kill("SIGTERM")}},Bt=e=>{let{cwd:t,onLog:r}=e,o=e.shell??le();r("opening pty session",{cwd:t,shell:o});try{return Ho({...e,shell:o})}catch(n){r("node-pty spawn failed, falling back to python pty",{cwd:t,shell:o,error:n instanceof Error?n.message:"unknown"})}try{return Fo({...e,shell:o})}catch(n){r("python pty spawn failed, falling back to pipe shell",{cwd:t,shell:o,error:n instanceof Error?n.message:"unknown"})}return Vo(e)};var g={connection:null,reconnectTimer:null,stopped:!1,connect:null},zo=new Map,At=e=>{let t=process.platform==="darwin"?["open",e]:process.platform==="win32"?["cmd","/c","start","",e]:["xdg-open",e];Ko(t[0],t.slice(1),{detached:!0,stdio:"ignore"}).unref()},qo=(e,t)=>{let r=["Vibemux Worker Preview","","Your local worker console is ready.",`Cloud console: ${e}`,`Local console: ${t}`,"","Opening both pages in your browser..."],o=Math.max(...r.map(i=>i.length)),n=`+${"-".repeat(o+2)}+`;console.log(n);for(let i of r)console.log(`| ${i.padEnd(o)} |`);console.log(n)},Ot=()=>{let e=m();Ce();let t=`http://127.0.0.1:${e.localServerPort}`;qo(e.cloudUrl,t),setTimeout(()=>{At(e.cloudUrl),At(t)},250)};var st=()=>{g.stopped=!0,g.reconnectTimer&&(clearTimeout(g.reconnectTimer),g.reconnectTimer=null),g.connection?.socket?.close(1e3,"manual disconnect"),g.connection=null,y({daemonMode:"disconnected",connected:!1,lastDisconnectAt:new Date().toISOString(),lastError:"Control plane connection was disconnected manually."})},Te=()=>{let e=m();if(!e.executorId||!e.executorToken)throw new Error("This worker is not paired yet, so it cannot connect to the control plane.");g.stopped=!1,g.reconnectTimer&&(clearTimeout(g.reconnectTimer),g.reconnectTimer=null),g.connection?.socket?.close(1e3,"manual reconnect"),g.connection=null,g.connect?.()},$t=()=>{g.stopped=!1;let e=m(),t=[],r=[],o=new Map,n=new Map,i=null,s=null,a=!1,c=new Date().toISOString();y({daemonMode:"starting",paired:!!(e.executorId&&e.executorToken),executorId:e.executorId,config:e,startedAt:c,lastConnectAttemptAt:c,lastError:void 0}),rt().catch(d=>{let f=d instanceof Error?d.message:"worker auto update failed";console.error("[worker] auto update failed",f)}),v({autoInstall:!0}).catch(d=>{let f=d instanceof Error?d.message:"worker runtime bootstrap failed";console.error("[worker] runtime bootstrap failed",f)}),te(e.workspaceRoot),Ce();let l=()=>{y({queuedTaskIds:r,runningTaskIds:t,lastTaskAt:new Date().toISOString()})},u=()=>{for(e=m();t.length<Math.max(1,e.maxConcurrency)&&r.length>0;){let d=r[0];r=r.slice(1);let f=o.get(d);if(!f)continue;t=[...t,d],l();let h=Tt({task:f,executorId:e.executorId,workspaceRoot:e.workspaceRoot,projectBindings:e.projectBindings,cloudUrl:e.cloudUrl,executorToken:e.executorToken,connection:i??g.connection,onStart(){},onFinish(b){o.delete(b),n.delete(b),t=t.filter(k=>k!==b),l(),u()}});n.set(d,h)}},p=d=>{a=g.stopped,!(a||s)&&(s=setTimeout(()=>{s=null,g.reconnectTimer=null,x()},5e3),g.reconnectTimer=s,y({daemonMode:"disconnected",connected:!1,paired:!0,lastDisconnectAt:new Date().toISOString(),lastError:d}))},x=()=>{if(!g.stopped){if(e=m(),!e.executorId||!e.executorToken){y({daemonMode:"unpaired",paired:!1,connected:!1,executorId:void 0,config:e,lastError:"This worker is not paired yet, so it cannot connect to the control plane."});return}y({daemonMode:"starting",connected:!1,paired:!0,executorId:e.executorId,config:e,lastConnectAttemptAt:new Date().toISOString()});try{let d=qe(e,{onOpen(){g.connection?.socket===d.socket&&(e=m(),y({daemonMode:"running",paired:!0,connected:!0,executorId:e.executorId,config:e,lastError:void 0}),d.send({type:"executor.register",executorId:e.executorId,capabilities:e.capabilities,labels:e.labels,workspaceRoot:e.workspaceRoot,maxConcurrency:e.maxConcurrency,sshPubkey:Be(),platform:process.platform,version:U()}))},onMessage(f){g.connection?.socket===d.socket&&It({expectedSocket:d.socket,getConnection:()=>i,getCurrentSocket:()=>g.connection?.socket,openTerminalSession:Bt,runTerminalCommand:Mt,terminalSessions:zo,assignedTasks:o,activeExecutions:n,getConfig:()=>e,setConfig:h=>{e=h},getQueuedTaskIds:()=>r,setQueuedTaskIds:h=>{r=h},getRunningTaskIds:()=>t,setRunningTaskIds:h=>{t=h},syncRuntimeState:l,drainAssignedQueue:u})(f)},onError(f){g.connection?.socket===d.socket&&(console.error("[worker] websocket error",f),p(f))},onClose(f){if(g.connection?.socket!==d.socket)return;let h=f.reason?.trim()||`code=${f.code}`;console.log("[worker] websocket disconnected from control plane",h),p(`Control plane connection closed (${h})`)}});i=d,g.connection=d}catch(d){let f=d instanceof Error?d.message:"worker websocket init failed";console.error("[worker] failed to start websocket connection",f),p(f)}}};g.connect=x,!e.executorId||!e.executorToken?(y({daemonMode:"unpaired",paired:!1,connected:!1}),console.log("[worker] not paired yet; open the local setup page and exchange a pairing code first")):x(),setInterval(()=>{if(!i||!R().connected)return;let d=new Date().toISOString();y({daemonMode:"running",connected:!0,paired:!0,lastHeartbeatAt:d,queuedTaskIds:r,runningTaskIds:t}),i.send({type:"executor.heartbeat",executorId:e.executorId,runningTaskIds:t,queuedTaskIds:r,sshPubkey:Be(),at:d})},15e3)};var _t=process.argv[2]||"daemon";switch(_t){case"daemon":{$t();break}case"open":{Ot();break}case"doctor":{Ie();break}case"reset":{Q(),console.log("[worker] local config cleared");break}case"update":{F().then(e=>{console.log(`[worker] ${e.message}`),e.applied&&setTimeout(()=>process.exit(0),250)});break}case"apply-update-internal":{let e=process.argv[3],t=process.argv[4],r=Number(process.argv[5]||"0");ot(e,t,r).catch(o=>{console.error(o instanceof Error?o.message:"worker update apply failed"),process.exitCode=1});break}default:console.error(`Unknown worker command: ${_t}`),process.exitCode=1}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vibemux-worker-preview",
|
|
3
|
+
"version": "0.1.0-preview.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"packageManager": "pnpm@10.24.0",
|
|
7
|
+
"bin": {
|
|
8
|
+
"vibemux-worker-preview": "./bin/cli.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"dist-worker",
|
|
13
|
+
"runtime"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=22"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@opencode-ai/sdk": "^1.2.25",
|
|
20
|
+
"dotenv": "^17.3.1",
|
|
21
|
+
"node-pty": "^1.1.0",
|
|
22
|
+
"simple-git": "^3.27.0"
|
|
23
|
+
}
|
|
24
|
+
}
|