vibemux-worker-preview 0.1.0-preview.3
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 +652 -0
- package/package.json +24 -0
- package/runtime/worker-release.json +6 -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,652 @@
|
|
|
1
|
+
import"dotenv/config";import{existsSync as _e,mkdirSync as Le,readFileSync as je,rmSync as Lt,writeFileSync as He}from"node:fs";import{randomUUID as jt}from"node:crypto";import Ge from"node:os";import D from"node:path";import{existsSync as ge,readFileSync as fe}from"node:fs";import I from"node:path";import{fileURLToPath as $t}from"node:url";var ue=null,pe=null,me=null,Nt=e=>{try{let t=JSON.parse(fe(e,"utf8"));return!!(t.name&&t.version)}catch{return!1}},Ut=()=>{let e=process.env.VIBEMUX_RUNTIME_ROOT?.trim();if(e)return I.resolve(e);let t=I.dirname($t(import.meta.url));for(;;){let r=I.join(t,"package.json");if(ge(r)&&Nt(r))return t;let o=I.dirname(t);if(o===t)return process.cwd();t=o}},R=()=>(ue||(ue=Ut()),ue),_t=()=>{if(!pe){let e=I.join(R(),"package.json");pe=ge(e)?JSON.parse(fe(e,"utf8")):{}}return pe},q=()=>{if(!me){let e=I.join(R(),"runtime","worker-release.json");me=ge(e)?JSON.parse(fe(e,"utf8")):{}}return me},_=()=>process.env.npm_package_version?.trim()||_t().version?.trim()||"0.0.0",he=()=>I.join(R(),"dist-worker","apps","worker","src","index.js"),Ne=()=>{let e=process.platform==="win32"?"vibemux-worker.cmd":"vibemux-worker";return I.join(R(),"bin",e)};var Ht=D.join(Ge.homedir(),".vibemux"),Gt=D.join(Ht,"worker"),zt="http://127.0.0.1:8989",Ft=()=>!!process.env.VIBEMUX_CLOUD_URL?.trim(),ze=()=>process.env.VIBEMUX_CLOUD_URL?.trim()||q().defaultCloudUrl?.trim()||zt,Vt=()=>{let e=process.env.VIBEMUX_WORKER_HOME?.trim();if(e)return e;let t=process.env.VIBEMUX_HOME?.trim();return t?D.join(t,"worker"):Gt},M=()=>D.resolve(Vt()),ke=()=>D.join(M(),"config.json"),Jt=()=>D.join(M(),"machine-id"),Kt=()=>{let e=M(),t=Jt();if(_e(t)){let o=je(t,"utf8").trim();if(o)return o}Le(e,{recursive:!0});let r=jt();return He(t,`${r}
|
|
2
|
+
`,"utf8"),r},Ue=()=>({cloudUrl:ze(),machineId:Kt(),machineName:Ge.hostname(),opencodeConfigContent:"",defaultModel:"",workspaceRoot:D.join(M(),"workspace"),maxConcurrency:1,labels:[],capabilities:["code-execution","git-operations"],localServerPort:Number(process.env.VIBEMUX_WORKER_PORT||48100),projectBindings:[]}),g=()=>{let e=ke();if(!_e(e))return Ue();let t=JSON.parse(je(e,"utf8")),r={...Ue(),...t};return Ft()&&(r.cloudUrl=ze()),r},H=e=>{let t=M();Le(t,{recursive:!0}),He(ke(),`${JSON.stringify(e,null,2)}
|
|
3
|
+
`,"utf8")},Q=()=>{Lt(ke(),{force:!0})};var ye=e=>e.replace(/\/+$/,""),qt=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`},Fe=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()},Ve=(e,t)=>{if(!e.executorToken)throw new Error("Worker is not paired yet.");let r=`${qt(e.cloudUrl)}?token=${encodeURIComponent(e.executorToken)}`,o=new WebSocket(r);return o.addEventListener("open",()=>{t.onOpen?.()}),o.addEventListener("message",async n=>{let s="";try{typeof n.data=="string"?s=n.data:n.data instanceof ArrayBuffer?s=Buffer.from(n.data).toString("utf8"):typeof Blob<"u"&&n.data instanceof Blob?s=await n.data.text():n.data&&typeof n.data.toString=="function"&&(s=n.data.toString());let i=JSON.parse(s);try{t.onMessage?.(i)}catch(a){t.onError?.(`\u63A7\u5236\u9762\u6D88\u606F\u5904\u7406\u5931\u8D25\u3002${a instanceof Error?` ${a.message}`:""}`)}}catch(i){t.onError?.(`\u63A7\u5236\u9762\u8FD4\u56DE\u4E86\u65E0\u6CD5\u89E3\u6790\u7684\u6D88\u606F\u3002${i instanceof Error?` ${i.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 fr from"node:http";import{spawnSync as Qt}from"node:child_process";import{existsSync as be}from"node:fs";var G=900*1e3,P=R(),E=(e,t,r)=>{let o=Qt(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,S=(e,t=["--version"])=>E(e,t).ok,qe=()=>{let e=E("git",["--version"]);return{id:"git",ok:e.ok,detail:A(e,"git \u4E0D\u53EF\u7528")}},Qe=()=>{let e=E(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")}},Xt=(e,t)=>typeof process.getuid=="function"&&process.getuid()===0?{command:e,args:t}:S("sudo",["-n","true"])?{command:"sudo",args:["-n",e,...t]}:null,L=(e,t)=>{let r=Xt(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=E(r.command,r.args,{timeout:G});return{ok:o.ok,changed:o.ok,detail:A(o,"git \u81EA\u52A8\u5B89\u88C5\u5931\u8D25")}},Yt=()=>{if(qe().ok)return{ok:!0,changed:!1,detail:"git \u5DF2\u5C31\u7EEA"};if(process.platform==="darwin"){if(!S("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=E("brew",["install","git"],{timeout:G});return{ok:e.ok,changed:e.ok,detail:A(e,"brew install git \u5931\u8D25")}}if(process.platform==="linux"){if(S("apt-get")){let e=L("apt-get",["update"]);return e.ok?L("apt-get",["install","-y","git"]):e}if(S("dnf"))return L("dnf",["install","-y","git"]);if(S("yum"))return L("yum",["install","-y","git"]);if(S("pacman"))return L("pacman",["-Sy","--noconfirm","git"]);if(S("apk"))return L("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`}},Zt=()=>{if(Qe().ok)return{ok:!0,changed:!1,detail:"OpenCode SDK \u5DF2\u5C31\u7EEA"};if(!be(`${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(be(`${P}/pnpm-lock.yaml`)&&S("pnpm")){let e=E("pnpm",["install","--frozen-lockfile"],{cwd:P,timeout:G});return{ok:e.ok,changed:e.ok,detail:A(e,"pnpm install \u5931\u8D25")}}if(be(`${P}/package-lock.json`)&&S("npm")){let e=E("npm",["ci"],{cwd:P,timeout:G});return{ok:e.ok,changed:e.ok,detail:A(e,"npm ci \u5931\u8D25")}}if(S("npm")){let e=E("npm",["install"],{cwd:P,timeout:G});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"}},Ke=()=>{let e=[qe(),Qe()],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")}`}},W=async e=>{let t=Ke();if(t.ok||!e?.autoInstall)return t;let r=new Map,o=!1;for(let i of t.items){if(i.ok)continue;let a=i.id==="git"?Yt():Zt();r.set(i.id,a.detail),o=o||a.changed}let n=Ke(),s=n.items.map(i=>{let a=r.get(i.id);return a?{...i,detail:`${i.detail}\uFF1B\u81EA\u52A8\u51C6\u5907\uFF1A${a}`}:i});return{ok:n.ok,changed:o,items:s,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${s.filter(i=>!i.ok).map(i=>i.id).join("\u3001")}`}};var X={daemonMode:"idle",paired:!1,connected:!1,runningTaskIds:[],queuedTaskIds:[]},T=()=>({...X,runningTaskIds:[...X.runningTaskIds],queuedTaskIds:[...X.queuedTaskIds]}),y=e=>{Object.assign(X,e)};var Y=null,er=(e,t)=>{let r=Number.parseInt(e||"0",10),o=Number.parseInt(t||"0",10);return r===o?0:r>o?1:-1},tr=(e,t)=>{let r=e.split(/[^0-9]+/).filter(Boolean),o=t.split(/[^0-9]+/).filter(Boolean),n=Math.max(r.length,o.length);for(let s=0;s<n;s+=1){let i=er(r[s]||"0",o[s]||"0");if(i!==0)return i}return 0},Xe=()=>Y||(Y=q(),Y),rr=()=>{let e=process.env.VIBEMUX_WORKER_UPDATE_MANIFEST_URL?.trim();return e||Xe().manifestUrl?.trim()},or=e=>e.assets.find(t=>t.platform===process.platform&&t.arch===process.arch),nr=()=>process.env.VIBEMUX_WORKER_RELEASE_CHANNEL?.trim()||Xe().channel?.trim()||"production",Z=async()=>{let e=_(),t=rr(),r=nr();if(!t)return{ok:!1,currentVersion:e,channel:r,available:!1,message:"\u672A\u914D\u7F6E Worker \u66F4\u65B0\u6E05\u5355\u5730\u5740\u3002"};try{let o=await fetch(t,{method:"GET",signal:AbortSignal.timeout(8e3)});if(!o.ok)return{ok:!1,currentVersion:e,manifestUrl:t,channel:r,available:!1,message:`\u66F4\u65B0\u6E05\u5355\u4E0D\u53EF\u7528\uFF0CHTTP ${o.status}`};let n=await o.json(),s=or(n);if(!s)return{ok:!1,currentVersion:e,latestVersion:n.version,manifestUrl:t,channel:n.channel,available:!1,message:`\u672A\u627E\u5230 ${process.platform}/${process.arch} \u7684 Worker \u5B89\u88C5\u5305\u3002`};let i=tr(n.version,e)>0;return{ok:!0,currentVersion:e,latestVersion:n.version,manifestUrl:t,channel:n.channel,available:i,asset:s,message:i?`\u53D1\u73B0\u65B0\u7248\u672C ${n.version}`:"\u5F53\u524D\u5DF2\u662F\u6700\u65B0\u7248\u672C\u3002"}}catch(o){return{ok:!1,currentVersion:e,manifestUrl:t,channel:r,available:!1,message:o instanceof Error?o.message:"\u66F4\u65B0\u68C0\u67E5\u5931\u8D25"}}};import{existsSync as Ye,mkdirSync as Ze,readdirSync as ir,renameSync as xe,rmSync as we,writeFileSync as sr}from"node:fs";import ar from"node:os";import O from"node:path";import{spawn as et,spawnSync as cr}from"node:child_process";var dr=e=>new Promise(t=>setTimeout(t,e)),lr=()=>Ye(Ne())&&Ye(he()),ur=async e=>{for(let t=0;t<120;t+=1)try{process.kill(e,0),await dr(500)}catch{return}throw new Error(`\u7B49\u5F85\u65E7 Worker \u9000\u51FA\u8D85\u65F6: ${e}`)},pr=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(ar.tmpdir(),"vibemux-worker-updates");Ze(o,{recursive:!0});let n=O.join(o,`worker-${Date.now()}.tar.gz`);return sr(n,r),n},mr=(e,t)=>{let r=cr("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")},gr=e=>{et(O.join(e,"bin","vibemux-worker"),["daemon"],{detached:!0,stdio:"ignore"}).unref()},z=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(!lr())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 pr(e.asset.url);return et(process.execPath,[he(),"apply-update-internal",t,R(),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`}},tt=async()=>{let e=process.env.VIBEMUX_WORKER_AUTO_UPDATE?.trim();if(e!=="1"&&e!=="true")return!1;let t=await z();return t.applied?(console.log(`[worker] ${t.message}`),setTimeout(()=>process.exit(0),250),!0):!1},rt=async(e,t,r)=>{await ur(r);let o=O.dirname(t),n=O.join(o,`.vibemux-worker-stage-${Date.now()}`),s=O.join(o,`.vibemux-worker-backup-${Date.now()}`);Ze(n,{recursive:!0}),mr(e,n);let[i]=ir(n);if(!i)throw new Error("\u66F4\u65B0\u5305\u5185\u5BB9\u4E3A\u7A7A");let a=O.join(n,i);xe(t,s);try{xe(a,t)}catch(c){throw xe(s,t),c}gr(t),we(s,{recursive:!0,force:!0}),we(n,{recursive:!0,force:!0}),we(e,{force:!0})};var hr=e=>new URL(e.url||"/","http://127.0.0.1"),ot=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"))},b=(e,t,r)=>{e.statusCode=t,e.setHeader("Content-Type","application/json; charset=utf-8"),e.end(`${JSON.stringify(r)}
|
|
4
|
+
`)},kr=()=>{let e=T(),t=g();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}},yr=()=>`<!doctype html>
|
|
5
|
+
<html lang="zh-CN">
|
|
6
|
+
<head>
|
|
7
|
+
<meta charset="utf-8" />
|
|
8
|
+
<title>Vibemux Worker</title>
|
|
9
|
+
<style>
|
|
10
|
+
:root {
|
|
11
|
+
color-scheme: dark;
|
|
12
|
+
font-family: "Geist", "SF Pro Display", "SF Pro Text", -apple-system, BlinkMacSystemFont, sans-serif;
|
|
13
|
+
--bg: #09090b;
|
|
14
|
+
--panel: rgba(24, 24, 27, 0.82);
|
|
15
|
+
--panel-strong: rgba(9, 9, 11, 0.92);
|
|
16
|
+
--panel-soft: rgba(39, 39, 42, 0.44);
|
|
17
|
+
--border: rgba(82, 82, 91, 0.7);
|
|
18
|
+
--border-soft: rgba(63, 63, 70, 0.5);
|
|
19
|
+
--text: #fafafa;
|
|
20
|
+
--muted: #a1a1aa;
|
|
21
|
+
--muted-strong: #d4d4d8;
|
|
22
|
+
--accent: #f4f4f5;
|
|
23
|
+
--accent-text: #09090b;
|
|
24
|
+
--success: #86efac;
|
|
25
|
+
--danger: #fda4af;
|
|
26
|
+
--warn: #fde68a;
|
|
27
|
+
}
|
|
28
|
+
* { box-sizing: border-box; }
|
|
29
|
+
html, body { min-height: 100%; }
|
|
30
|
+
body {
|
|
31
|
+
margin: 0;
|
|
32
|
+
color: var(--text);
|
|
33
|
+
background:
|
|
34
|
+
radial-gradient(circle at top, rgba(63, 63, 70, 0.42), transparent 34%),
|
|
35
|
+
radial-gradient(circle at 80% 0%, rgba(113, 113, 122, 0.18), transparent 28%),
|
|
36
|
+
linear-gradient(180deg, rgba(9, 9, 11, 0.94) 0%, rgba(9, 9, 11, 1) 100%);
|
|
37
|
+
}
|
|
38
|
+
body::before {
|
|
39
|
+
content: "";
|
|
40
|
+
position: fixed;
|
|
41
|
+
inset: 0;
|
|
42
|
+
pointer-events: none;
|
|
43
|
+
background-image: linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px);
|
|
44
|
+
background-size: 28px 28px;
|
|
45
|
+
mask-image: linear-gradient(180deg, rgba(0,0,0,0.55), transparent 88%);
|
|
46
|
+
}
|
|
47
|
+
main { max-width: 1320px; margin: 0 auto; padding: 28px 20px 64px; position: relative; }
|
|
48
|
+
h1, h2, h3, p { margin-top: 0; }
|
|
49
|
+
h1 { margin-bottom: 12px; font-size: clamp(32px, 5vw, 52px); letter-spacing: -0.04em; line-height: 0.95; }
|
|
50
|
+
h2 { margin-bottom: 4px; font-size: 18px; letter-spacing: -0.02em; }
|
|
51
|
+
p { color: var(--muted); line-height: 1.6; }
|
|
52
|
+
label { display: grid; gap: 8px; font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.12em; }
|
|
53
|
+
.shell { display: grid; gap: 18px; }
|
|
54
|
+
.hero {
|
|
55
|
+
position: relative;
|
|
56
|
+
overflow: hidden;
|
|
57
|
+
display: grid;
|
|
58
|
+
gap: 22px;
|
|
59
|
+
grid-template-columns: minmax(0, 1.35fr) minmax(300px, 0.65fr);
|
|
60
|
+
padding: 28px;
|
|
61
|
+
border-radius: 30px;
|
|
62
|
+
border: 1px solid var(--border);
|
|
63
|
+
background: linear-gradient(180deg, rgba(24,24,27,0.88) 0%, rgba(9,9,11,0.94) 100%);
|
|
64
|
+
box-shadow: 0 24px 70px rgba(0,0,0,0.28);
|
|
65
|
+
}
|
|
66
|
+
.hero::after {
|
|
67
|
+
content: "";
|
|
68
|
+
position: absolute;
|
|
69
|
+
inset: auto -10% -30% auto;
|
|
70
|
+
width: 360px;
|
|
71
|
+
height: 360px;
|
|
72
|
+
border-radius: 999px;
|
|
73
|
+
background: radial-gradient(circle, rgba(255,255,255,0.12), transparent 66%);
|
|
74
|
+
filter: blur(4px);
|
|
75
|
+
}
|
|
76
|
+
.hero-copy { position: relative; z-index: 1; display: grid; gap: 16px; }
|
|
77
|
+
.eyebrow { display: inline-flex; width: fit-content; align-items: center; gap: 8px; padding: 7px 12px; border-radius: 999px; background: rgba(244,244,245,0.08); border: 1px solid rgba(244,244,245,0.10); color: var(--muted-strong); font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; }
|
|
78
|
+
.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; }
|
|
79
|
+
.hero-side { position: relative; z-index: 1; display: grid; gap: 12px; align-content: start; }
|
|
80
|
+
.spotlight { padding: 18px; border-radius: 22px; border: 1px solid var(--border-soft); background: var(--panel-soft); backdrop-filter: blur(12px); }
|
|
81
|
+
.spotlight .kicker { display: block; margin-bottom: 8px; color: var(--muted); font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; }
|
|
82
|
+
.spotlight strong { display: block; font-size: 24px; letter-spacing: -0.03em; }
|
|
83
|
+
.spotlight p { margin-bottom: 0; font-size: 13px; }
|
|
84
|
+
.grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 18px; }
|
|
85
|
+
.card {
|
|
86
|
+
position: relative;
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
background: linear-gradient(180deg, rgba(24,24,27,0.78) 0%, rgba(9,9,11,0.92) 100%);
|
|
89
|
+
border: 1px solid var(--border-soft);
|
|
90
|
+
border-radius: 26px;
|
|
91
|
+
padding: 22px;
|
|
92
|
+
box-shadow: 0 20px 54px rgba(0,0,0,0.18);
|
|
93
|
+
}
|
|
94
|
+
.card-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; margin-bottom: 18px; }
|
|
95
|
+
.card-title p { margin-bottom: 0; font-size: 13px; }
|
|
96
|
+
.span-4 { grid-column: span 4; }
|
|
97
|
+
.span-5 { grid-column: span 5; }
|
|
98
|
+
.span-7 { grid-column: span 7; }
|
|
99
|
+
.span-8 { grid-column: span 8; }
|
|
100
|
+
.span-12 { grid-column: span 12; }
|
|
101
|
+
.stats { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 12px; }
|
|
102
|
+
.stat { padding: 16px; border-radius: 18px; background: rgba(24,24,27,0.92); border: 1px solid rgba(63,63,70,0.8); }
|
|
103
|
+
.stat.primary { background: linear-gradient(180deg, rgba(244,244,245,0.08), rgba(24,24,27,0.94)); }
|
|
104
|
+
.label { font-size: 11px; text-transform: uppercase; letter-spacing: .14em; color: var(--muted); margin-bottom: 8px; }
|
|
105
|
+
.value { font-size: clamp(20px, 2.8vw, 28px); font-weight: 700; letter-spacing: -0.04em; }
|
|
106
|
+
.value.small { font-size: 16px; line-height: 1.4; }
|
|
107
|
+
.muted { color: var(--muted); }
|
|
108
|
+
.row { display: grid; gap: 14px; margin-bottom: 14px; }
|
|
109
|
+
.row.two { grid-template-columns: repeat(2, minmax(0,1fr)); }
|
|
110
|
+
.row.three { grid-template-columns: repeat(3, minmax(0,1fr)); }
|
|
111
|
+
input, textarea {
|
|
112
|
+
width: 100%;
|
|
113
|
+
padding: 13px 14px;
|
|
114
|
+
border-radius: 16px;
|
|
115
|
+
border: 1px solid rgba(63,63,70,0.9);
|
|
116
|
+
background: rgba(9,9,11,0.9);
|
|
117
|
+
color: var(--text);
|
|
118
|
+
outline: none;
|
|
119
|
+
transition: border-color .18s ease, box-shadow .18s ease, transform .18s ease;
|
|
120
|
+
}
|
|
121
|
+
input:focus, textarea:focus { border-color: rgba(212,212,216,0.5); box-shadow: 0 0 0 4px rgba(244,244,245,0.08); }
|
|
122
|
+
textarea { min-height: 110px; resize: vertical; }
|
|
123
|
+
button {
|
|
124
|
+
appearance: none;
|
|
125
|
+
border: 1px solid rgba(244,244,245,0.08);
|
|
126
|
+
cursor: pointer;
|
|
127
|
+
border-radius: 999px;
|
|
128
|
+
padding: 12px 16px;
|
|
129
|
+
font-weight: 700;
|
|
130
|
+
letter-spacing: -0.01em;
|
|
131
|
+
background: var(--accent);
|
|
132
|
+
color: var(--accent-text);
|
|
133
|
+
transition: transform .18s ease, opacity .18s ease, background .18s ease, border-color .18s ease;
|
|
134
|
+
}
|
|
135
|
+
button:hover { transform: translateY(-1px); }
|
|
136
|
+
button:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
|
|
137
|
+
button.secondary { background: rgba(24,24,27,0.88); color: var(--text); border: 1px solid rgba(82,82,91,0.9); }
|
|
138
|
+
button.warn { background: rgba(127,29,29,0.45); color: #ffe4e6; border-color: rgba(244,63,94,0.26); }
|
|
139
|
+
.actions { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
140
|
+
.actions.stacked { margin-top: 6px; }
|
|
141
|
+
code, pre { background: rgba(24,24,27,0.92); border: 1px solid rgba(63,63,70,0.75); border-radius: 14px; }
|
|
142
|
+
code { padding: 2px 6px; }
|
|
143
|
+
pre { padding: 14px; overflow: auto; font-size: 12px; color: var(--muted-strong); }
|
|
144
|
+
table { width: 100%; border-collapse: collapse; }
|
|
145
|
+
th, td { padding: 12px 8px; border-bottom: 1px solid rgba(63,63,70,0.55); text-align: left; vertical-align: top; color: var(--muted-strong); }
|
|
146
|
+
th { font-size: 11px; text-transform: uppercase; letter-spacing: 0.12em; color: var(--muted); }
|
|
147
|
+
.pill { display: inline-flex; width: fit-content; align-items: center; gap: 6px; border-radius: 999px; padding: 7px 10px; font-size: 11px; border: 1px solid rgba(82,82,91,0.75); background: rgba(24,24,27,0.9); color: var(--muted-strong); }
|
|
148
|
+
.pill.ok { border-color: rgba(134,239,172,0.25); color: var(--success); }
|
|
149
|
+
.pill.bad { border-color: rgba(253,164,175,0.25); color: var(--danger); }
|
|
150
|
+
.pill.waiting { border-color: rgba(253,230,138,0.24); color: var(--warn); }
|
|
151
|
+
.pill.neutral { color: var(--muted-strong); }
|
|
152
|
+
.statusbar { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 16px; }
|
|
153
|
+
.statusbar .pill { padding-right: 12px; }
|
|
154
|
+
.cloud-dot { width: 8px; height: 8px; border-radius: 999px; background: currentColor; box-shadow: 0 0 0 4px rgba(255,255,255,0.04); }
|
|
155
|
+
.ok { color: var(--success); }
|
|
156
|
+
.bad { color: var(--danger); }
|
|
157
|
+
.note { font-size: 13px; color: var(--muted); }
|
|
158
|
+
.meta-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
|
|
159
|
+
.meta-item { padding: 14px; border-radius: 18px; border: 1px solid rgba(63,63,70,0.6); background: rgba(9,9,11,0.64); }
|
|
160
|
+
.meta-item .label { margin-bottom: 6px; }
|
|
161
|
+
.doctor-summary { display: grid; gap: 14px; }
|
|
162
|
+
.doctor-banner { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 16px 18px; border-radius: 20px; border: 1px solid rgba(82,82,91,0.65); background: rgba(24,24,27,0.9); }
|
|
163
|
+
.doctor-banner.ok { border-color: rgba(134,239,172,0.28); background: linear-gradient(180deg, rgba(20,83,45,0.32), rgba(24,24,27,0.9)); }
|
|
164
|
+
.doctor-banner.bad { border-color: rgba(253,164,175,0.28); background: linear-gradient(180deg, rgba(127,29,29,0.26), rgba(24,24,27,0.9)); }
|
|
165
|
+
.doctor-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
|
|
166
|
+
.doctor-item { border-radius: 20px; padding: 16px; border: 1px solid rgba(63,63,70,0.7); background: rgba(9,9,11,0.72); display: grid; gap: 8px; }
|
|
167
|
+
.doctor-item.ok { border-color: rgba(134,239,172,0.22); }
|
|
168
|
+
.doctor-item.bad { border-color: rgba(253,164,175,0.22); }
|
|
169
|
+
.doctor-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
170
|
+
.doctor-title { font-size: 14px; font-weight: 700; color: var(--text); }
|
|
171
|
+
.doctor-detail { font-size: 13px; color: var(--muted-strong); line-height: 1.55; word-break: break-word; }
|
|
172
|
+
.doctor-hint { font-size: 12px; color: var(--muted); line-height: 1.5; }
|
|
173
|
+
details.raw { border-top: 1px solid rgba(63,63,70,0.55); padding-top: 10px; }
|
|
174
|
+
details.raw summary { cursor: pointer; color: var(--muted-strong); }
|
|
175
|
+
.empty-state { padding: 18px; border-radius: 18px; border: 1px dashed rgba(82,82,91,0.8); color: var(--muted); background: rgba(9,9,11,0.48); text-align: center; }
|
|
176
|
+
.toast { position: fixed; right: 18px; bottom: 18px; max-width: 360px; padding: 14px 16px; border-radius: 18px; background: rgba(9,9,11,0.96); border: 1px solid rgba(82,82,91,0.9); display: none; box-shadow: 0 18px 48px rgba(0,0,0,0.28); }
|
|
177
|
+
@media (max-width: 1080px) { .hero { grid-template-columns: 1fr; } }
|
|
178
|
+
@media (max-width: 920px) { .span-4,.span-5,.span-7,.span-8,.span-12 { grid-column: span 12; } .stats,.row.two,.row.three,.doctor-grid,.meta-grid { grid-template-columns: 1fr; } }
|
|
179
|
+
</style>
|
|
180
|
+
</head>
|
|
181
|
+
<body>
|
|
182
|
+
<main>
|
|
183
|
+
<div class="shell">
|
|
184
|
+
<section class="hero">
|
|
185
|
+
<div class="hero-copy">
|
|
186
|
+
<span class="eyebrow">Local Runtime Console</span>
|
|
187
|
+
<div>
|
|
188
|
+
<h1>\u8BA9\u672C\u5730 Worker \u7684\u72B6\u6001\uFF0C\u50CF\u4E91\u7AEF\u63A7\u5236\u53F0\u4E00\u6837\u6E05\u6670\u3002</h1>
|
|
189
|
+
<p>\u5728\u4E00\u4E2A\u9875\u9762\u91CC\u5B8C\u6210\u8FDE\u63A5\u3001\u914D\u5BF9\u3001\u5DE5\u4F5C\u533A\u914D\u7F6E\u3001\u9879\u76EE\u7ED1\u5B9A\u548C\u8BCA\u65AD\u6392\u969C\u3002\u5E03\u5C40\u548C\u89C6\u89C9\u8BED\u8A00\u5411\u4E3B\u4EA7\u54C1\u540E\u53F0\u9760\u62E2\uFF0C\u4F18\u5148\u7A81\u51FA\u8FD0\u884C\u6001\u548C\u5F02\u5E38\u4FE1\u606F\u3002</p>
|
|
190
|
+
</div>
|
|
191
|
+
<div class="hero-actions">
|
|
192
|
+
<button class="secondary" id="refreshButton">\u5237\u65B0\u72B6\u6001</button>
|
|
193
|
+
<button class="secondary" id="doctorButton">\u8FD0\u884C\u81EA\u68C0</button>
|
|
194
|
+
<button class="secondary" id="bootstrapButton">\u81EA\u52A8\u51C6\u5907\u73AF\u5883</button>
|
|
195
|
+
<button id="connectButton">\u8FDE\u63A5\u4E91\u7AEF</button>
|
|
196
|
+
<button class="warn" id="disconnectButton">\u65AD\u5F00\u8FDE\u63A5</button>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
<div class="hero-side">
|
|
200
|
+
<div class="spotlight">
|
|
201
|
+
<span class="kicker">\u5165\u53E3</span>
|
|
202
|
+
<strong>\u5DE5\u4F5C\u7AD9\u63A7\u5236\u53F0</strong>
|
|
203
|
+
<p>\u4E13\u95E8\u4E3A\u72EC\u7ACB\u5DE5\u4F5C\u7AD9\u51C6\u5907\u7684\u672C\u673A\u63A7\u5236\u9875\uFF0C\u9002\u5408\u5728\u4EFB\u52A1\u5361\u4F4F\u3001\u914D\u5BF9\u5931\u8D25\u6216\u5FC3\u8DF3\u5F02\u5E38\u65F6\u4F18\u5148\u6253\u5F00\u3002</p>
|
|
204
|
+
</div>
|
|
205
|
+
<div class="spotlight">
|
|
206
|
+
<span class="kicker">\u5EFA\u8BAE\u6392\u969C\u987A\u5E8F</span>
|
|
207
|
+
<p>\u5148\u770B\u8FDE\u63A5\u72B6\u6001\uFF0C\u518D\u8DD1\u81EA\u68C0\uFF0C\u518D\u68C0\u67E5\u914D\u5BF9\u548C workspace\uFF0C\u6700\u540E\u5904\u7406 project bindings\u3002</p>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
</section>
|
|
211
|
+
|
|
212
|
+
<div class="grid">
|
|
213
|
+
<section class="card span-12">
|
|
214
|
+
<div class="card-header">
|
|
215
|
+
<div class="card-title">
|
|
216
|
+
<h2>\u8FD0\u884C\u72B6\u6001</h2>
|
|
217
|
+
<p>\u96C6\u4E2D\u5C55\u793A daemon\u3001\u8FDE\u63A5\u6001\u3001\u4EFB\u52A1\u961F\u5217\u548C\u6700\u8FD1\u4E00\u6B21\u8FD0\u884C\u8F68\u8FF9\u3002</p>
|
|
218
|
+
</div>
|
|
219
|
+
<div class="pill neutral" id="heroRuntimePill">\u7B49\u5F85\u5237\u65B0</div>
|
|
220
|
+
</div>
|
|
221
|
+
<div class="statusbar" id="cloudStatus"></div>
|
|
222
|
+
<div class="stats" id="stats"></div>
|
|
223
|
+
<div class="meta-grid" id="runtimeMeta"></div>
|
|
224
|
+
</section>
|
|
225
|
+
|
|
226
|
+
<section class="card span-5">
|
|
227
|
+
<div class="card-header">
|
|
228
|
+
<div class="card-title">
|
|
229
|
+
<h2>Pairing</h2>
|
|
230
|
+
<p>\u628A\u672C\u5730\u5DE5\u4F5C\u7AD9\u63A5\u5165\u63A7\u5236\u9762\uFF0C\u62FF\u5230\u7A33\u5B9A\u7684\u5DE5\u4F5C\u7AD9\u8EAB\u4EFD\u3002</p>
|
|
231
|
+
</div>
|
|
232
|
+
<div class="pill neutral" id="pairingPill">\u672A\u68C0\u67E5</div>
|
|
233
|
+
</div>
|
|
234
|
+
<div class="row">
|
|
235
|
+
<label>Pairing Code<input id="pairingCode" placeholder="\u8F93\u5165\u63A7\u5236\u9762\u751F\u6210\u7684 pairing code" /></label>
|
|
236
|
+
<label>Worker Name<input id="pairingName" placeholder="\u4F8B\u5982 MacBook-Air-Worker" /></label>
|
|
237
|
+
</div>
|
|
238
|
+
<div class="actions stacked">
|
|
239
|
+
<button id="pairButton">\u914D\u5BF9\u8FDE\u63A5</button>
|
|
240
|
+
<button class="warn" id="resetButton">\u6E05\u7A7A\u672C\u5730\u914D\u7F6E</button>
|
|
241
|
+
</div>
|
|
242
|
+
<pre id="pairingMeta"></pre>
|
|
243
|
+
</section>
|
|
244
|
+
|
|
245
|
+
<section class="card span-7">
|
|
246
|
+
<div class="card-header">
|
|
247
|
+
<div class="card-title">
|
|
248
|
+
<h2>\u57FA\u7840\u914D\u7F6E</h2>
|
|
249
|
+
<p>\u7EDF\u4E00\u7BA1\u7406 Cloud URL\u3001workspace \u548C\u6267\u884C\u80FD\u529B\uFF0C\u4FDD\u6301\u548C\u4E91\u7AEF\u8FD0\u884C\u65F6\u914D\u7F6E\u4E00\u81F4\u3002</p>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
<div class="row two">
|
|
253
|
+
<label>Cloud URL<input id="cloudUrl" /></label>
|
|
254
|
+
<label>Workspace Root<input id="workspaceRoot" /></label>
|
|
255
|
+
</div>
|
|
256
|
+
<div class="row three">
|
|
257
|
+
<label>Max Concurrency<input id="maxConcurrency" type="number" min="1" /></label>
|
|
258
|
+
<label>Labels<textarea id="labels" placeholder="\u6BCF\u884C\u4E00\u4E2A label"></textarea></label>
|
|
259
|
+
<label>Capabilities<textarea id="capabilities" placeholder="\u6BCF\u884C\u4E00\u4E2A capability"></textarea></label>
|
|
260
|
+
</div>
|
|
261
|
+
<div class="actions"><button id="saveConfigButton">\u4FDD\u5B58\u914D\u7F6E</button></div>
|
|
262
|
+
</section>
|
|
263
|
+
|
|
264
|
+
<section class="card span-7">
|
|
265
|
+
<div class="card-header">
|
|
266
|
+
<div class="card-title">
|
|
267
|
+
<h2>Project Bindings</h2>
|
|
268
|
+
<p>\u547D\u4E2D <code>projectId</code> \u6216 <code>repoUrl</code> \u65F6\uFF0C\u4F18\u5148\u590D\u7528\u672C\u5730\u4ED3\u5E93\uFF0C\u51CF\u5C11 clone \u548C worktree \u9884\u70ED\u3002</p>
|
|
269
|
+
</div>
|
|
270
|
+
<div class="pill neutral">\u672C\u5730\u8DEF\u5F84\u4F18\u5148</div>
|
|
271
|
+
</div>
|
|
272
|
+
<table>
|
|
273
|
+
<thead><tr><th>Project ID</th><th>Repo URL</th><th>Local Path</th><th></th></tr></thead>
|
|
274
|
+
<tbody id="bindingsBody"></tbody>
|
|
275
|
+
</table>
|
|
276
|
+
<div class="actions" style="margin-top:14px"><button class="secondary" id="addBindingButton">\u65B0\u589E\u7ED1\u5B9A</button><button id="saveBindingsButton">\u4FDD\u5B58\u7ED1\u5B9A</button></div>
|
|
277
|
+
</section>
|
|
278
|
+
|
|
279
|
+
<section class="card span-5">
|
|
280
|
+
<div class="card-header">
|
|
281
|
+
<div class="card-title">
|
|
282
|
+
<h2>\u8BCA\u65AD\u9762\u677F</h2>
|
|
283
|
+
<p>\u628A\u5DE5\u5177\u3001\u6587\u4EF6\u7CFB\u7EDF\u3001\u914D\u7F6E\u548C\u7F51\u7EDC\u95EE\u9898\u6C47\u603B\u5230\u4E00\u5904\u3002</p>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
<div id="doctorOutput" class="doctor-summary"></div>
|
|
287
|
+
</section>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
</main>
|
|
291
|
+
<div class="toast" id="toast"></div>
|
|
292
|
+
<script>
|
|
293
|
+
const state = { config: null, runtime: null }
|
|
294
|
+
const $ = (id) => document.getElementById(id)
|
|
295
|
+
const toast = (message, ok = true) => {
|
|
296
|
+
const el = $('toast')
|
|
297
|
+
el.textContent = message
|
|
298
|
+
el.style.display = 'block'
|
|
299
|
+
el.style.borderColor = ok ? 'rgba(134,239,172,.35)' : 'rgba(251,113,133,.35)'
|
|
300
|
+
setTimeout(() => { el.style.display = 'none' }, 2800)
|
|
301
|
+
}
|
|
302
|
+
const toLines = (value) => (value || []).join('\\n')
|
|
303
|
+
const fromLines = (value) => value.split('\\n').map((item) => item.trim()).filter(Boolean)
|
|
304
|
+
const escapeHtml = (value) => String(value).replace(/[&<>"]/g, (char) => ({ '&':'&','<':'<','>':'>','"':'"' }[char]))
|
|
305
|
+
const runtimePill = (runtime) => {
|
|
306
|
+
if (!runtime?.paired) return ['waiting', '\u672A\u914D\u5BF9']
|
|
307
|
+
if (runtime?.connected) return ['ok', '\u4E91\u7AEF\u5728\u7EBF']
|
|
308
|
+
return ['bad', '\u4E91\u7AEF\u65AD\u5F00']
|
|
309
|
+
}
|
|
310
|
+
const yesNo = (value) => value ? '\u5DF2\u542F\u7528' : '\u672A\u542F\u7528'
|
|
311
|
+
|
|
312
|
+
const renderStats = () => {
|
|
313
|
+
const runtime = state.runtime || {}
|
|
314
|
+
const config = state.config || {}
|
|
315
|
+
const cloudState = runtimePill(runtime)
|
|
316
|
+
$('stats').innerHTML = [
|
|
317
|
+
['Daemon Mode', runtime.daemonMode || 'idle', 'primary'],
|
|
318
|
+
['Pairing', runtime.paired ? 'Ready' : 'Pending'],
|
|
319
|
+
['Cloud Session', runtime.connected ? 'Connected' : 'Offline'],
|
|
320
|
+
['Running / Queued', String((runtime.runningTaskIds || []).length) + ' / ' + String((runtime.queuedTaskIds || []).length)],
|
|
321
|
+
].map(([label, value, extra]) => '<div class="stat ' + (extra || '') + '"><div class="label">' + escapeHtml(label) + '</div><div class="value">' + escapeHtml(value) + '</div></div>').join('')
|
|
322
|
+
$('cloudStatus').innerHTML = [
|
|
323
|
+
'<div class="pill ' + cloudState[0] + '"><span class="cloud-dot"></span>' + escapeHtml(cloudState[1]) + '</div>',
|
|
324
|
+
'<div class="pill neutral">Machine ' + escapeHtml(config.machineName || 'n/a') + '</div>',
|
|
325
|
+
'<div class="pill neutral">\u5DE5\u4F5C\u7AD9 ' + escapeHtml(config.executorId || runtime.executorId || 'unpaired') + '</div>',
|
|
326
|
+
].join('')
|
|
327
|
+
$('heroRuntimePill').className = 'pill ' + cloudState[0]
|
|
328
|
+
$('heroRuntimePill').textContent = cloudState[1]
|
|
329
|
+
$('pairingPill').className = 'pill ' + (runtime.paired ? 'ok' : 'waiting')
|
|
330
|
+
$('pairingPill').textContent = runtime.paired ? '\u5DF2\u914D\u5BF9' : '\u7B49\u5F85 pairing'
|
|
331
|
+
$('runtimeMeta').innerHTML = [
|
|
332
|
+
['Cloud URL', config.cloudUrl || 'n/a'],
|
|
333
|
+
['Workspace', config.workspaceRoot || 'n/a'],
|
|
334
|
+
['Machine ID', config.machineId || 'n/a'],
|
|
335
|
+
['Last Connect Attempt', runtime.lastConnectAttemptAt || 'n/a'],
|
|
336
|
+
['Last Heartbeat', runtime.lastHeartbeatAt || 'n/a'],
|
|
337
|
+
['Last Disconnect', runtime.lastDisconnectAt || 'n/a'],
|
|
338
|
+
['Last Task Activity', runtime.lastTaskAt || 'n/a'],
|
|
339
|
+
['Last Error', runtime.lastError ? runtime.lastError : 'none'],
|
|
340
|
+
].map(([label, value]) => '<div class="meta-item"><div class="label">' + escapeHtml(label) + '</div><div class="value small ' + (label === 'Last Error' && value !== 'none' ? 'bad' : '') + '">' + escapeHtml(value) + '</div></div>').join('')
|
|
341
|
+
$('connectButton').disabled = Boolean(runtime.connected) || !Boolean(runtime.paired)
|
|
342
|
+
$('disconnectButton').disabled = !Boolean(runtime.connected)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const renderBindings = () => {
|
|
346
|
+
const bindings = (state.config && state.config.projectBindings) || []
|
|
347
|
+
$('bindingsBody').innerHTML = bindings.map((binding, index) => '<tr>' +
|
|
348
|
+
'<td><input data-field="projectId" data-index="' + index + '" value="' + escapeHtml(binding.projectId || '') + '" /></td>' +
|
|
349
|
+
'<td><input data-field="repoUrl" data-index="' + index + '" value="' + escapeHtml(binding.repoUrl || '') + '" /></td>' +
|
|
350
|
+
'<td><input data-field="localPath" data-index="' + index + '" value="' + escapeHtml(binding.localPath || '') + '" /></td>' +
|
|
351
|
+
'<td><button class="secondary" data-remove="' + index + '">\u5220\u9664</button></td>' +
|
|
352
|
+
'</tr>').join('')
|
|
353
|
+
if (!bindings.length) {
|
|
354
|
+
$('bindingsBody').innerHTML = '<tr><td colspan="4"><div class="empty-state">\u8FD8\u6CA1\u6709 project binding\uFF0C\u9002\u5408\u4E3A\u5E38\u7528\u4ED3\u5E93\u9884\u5148\u7ED1\u5B9A\u672C\u5730\u76EE\u5F55\u3002</div></td></tr>'
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const renderDoctor = (payload) => {
|
|
359
|
+
const summary = payload.summary || { total: 0, passed: 0, failed: 0, ok: false }
|
|
360
|
+
const items = Array.isArray(payload.items) ? payload.items : []
|
|
361
|
+
const cloudMessage = payload.cloudProbe?.message || '\u672A\u6267\u884C\u63A7\u5236\u9762\u63A2\u6D4B'
|
|
362
|
+
const officialMessage = payload.officialSiteProbe?.message || '\u672A\u6267\u884C\u5B98\u7F51\u63A2\u6D4B'
|
|
363
|
+
const bannerClass = summary.ok ? 'ok' : 'bad'
|
|
364
|
+
$('doctorOutput').innerHTML = [
|
|
365
|
+
'<div class="doctor-banner ' + bannerClass + '">',
|
|
366
|
+
'<div><div class="doctor-title">' + escapeHtml(summary.ok ? '\u81EA\u68C0\u901A\u8FC7' : '\u81EA\u68C0\u53D1\u73B0\u95EE\u9898') + '</div><div class="doctor-detail">' + escapeHtml('\u901A\u8FC7 ' + summary.passed + ' / ' + summary.total + '\uFF0C\u5931\u8D25 ' + summary.failed + ' \u9879') + '</div></div>',
|
|
367
|
+
'<span class="pill ' + bannerClass + '">' + escapeHtml(summary.ok ? 'READY' : 'CHECK') + '</span>',
|
|
368
|
+
'</div>',
|
|
369
|
+
'<div class="doctor-grid">',
|
|
370
|
+
items.map((item) => '<div class="doctor-item ' + (item.ok ? 'ok' : 'bad') + '">' +
|
|
371
|
+
'<div class="doctor-head"><span class="doctor-title">' + escapeHtml(item.label || item.id || '\u68C0\u67E5\u9879') + '</span><span class="pill">' + escapeHtml(item.ok ? 'OK' : 'FAIL') + '</span></div>' +
|
|
372
|
+
'<div class="doctor-detail">' + escapeHtml(item.detail || '') + '</div>' +
|
|
373
|
+
(item.hint ? '<div class="doctor-hint">' + escapeHtml(item.hint) + '</div>' : '') +
|
|
374
|
+
'</div>').join(''),
|
|
375
|
+
'</div>',
|
|
376
|
+
'<div class="note">\u63A7\u5236\u9762: ' + escapeHtml(cloudMessage) + '<br/>OpenCode \u5B98\u7F51: ' + escapeHtml(officialMessage) + '</div>',
|
|
377
|
+
'<details class="raw"><summary>\u67E5\u770B\u539F\u59CB doctor JSON</summary><pre>' + escapeHtml(JSON.stringify(payload, null, 2)) + '</pre></details>',
|
|
378
|
+
].join('')
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const fillConfig = () => {
|
|
382
|
+
const config = state.config || {}
|
|
383
|
+
$('cloudUrl').value = config.cloudUrl || ''
|
|
384
|
+
$('workspaceRoot').value = config.workspaceRoot || ''
|
|
385
|
+
$('maxConcurrency').value = String(config.maxConcurrency || 1)
|
|
386
|
+
$('labels').value = toLines(config.labels)
|
|
387
|
+
$('capabilities').value = toLines(config.capabilities)
|
|
388
|
+
$('pairingName').value = $('pairingName').value || config.executorName || config.machineName || ''
|
|
389
|
+
$('pairingMeta').textContent = JSON.stringify({ machineId: config.machineId, machineName: config.machineName, executorId: config.executorId, workspaceRoot: config.workspaceRoot, localServerPort: config.localServerPort }, null, 2)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const getBindingsFromTable = () => Array.from($('bindingsBody').querySelectorAll('tr')).map((row) => {
|
|
393
|
+
const get = (field) => row.querySelector('[data-field="' + field + '"]')?.value?.trim() || ''
|
|
394
|
+
const localPath = get('localPath')
|
|
395
|
+
if (!localPath) return null
|
|
396
|
+
return { projectId: get('projectId') || undefined, repoUrl: get('repoUrl') || undefined, localPath }
|
|
397
|
+
}).filter(Boolean)
|
|
398
|
+
|
|
399
|
+
const fetchJson = async (url, options) => {
|
|
400
|
+
const response = await fetch(url, options)
|
|
401
|
+
const payload = await response.json().catch(() => ({}))
|
|
402
|
+
if (!response.ok) throw new Error(payload.message || 'request failed')
|
|
403
|
+
return payload
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const refresh = async () => {
|
|
407
|
+
const [configPayload, statusPayload] = await Promise.all([
|
|
408
|
+
fetchJson('/api/config'),
|
|
409
|
+
fetchJson('/api/status'),
|
|
410
|
+
])
|
|
411
|
+
state.config = configPayload.config
|
|
412
|
+
state.runtime = statusPayload.runtime
|
|
413
|
+
fillConfig()
|
|
414
|
+
renderStats()
|
|
415
|
+
renderBindings()
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
$('refreshButton').onclick = async () => { await refresh(); toast('\u72B6\u6001\u5DF2\u5237\u65B0') }
|
|
419
|
+
$('doctorButton').onclick = async () => {
|
|
420
|
+
const payload = await fetchJson('/api/doctor')
|
|
421
|
+
renderDoctor(payload)
|
|
422
|
+
toast(payload.summary?.ok ? '\u81EA\u68C0\u5B8C\u6210\uFF0C\u4E00\u5207\u6B63\u5E38' : '\u81EA\u68C0\u5B8C\u6210\uFF0C\u6709\u95EE\u9898\u9700\u8981\u5904\u7406', Boolean(payload.summary?.ok))
|
|
423
|
+
}
|
|
424
|
+
$('bootstrapButton').onclick = async () => {
|
|
425
|
+
const payload = await fetchJson('/api/bootstrap-runtime', { method: 'POST' })
|
|
426
|
+
renderDoctor(payload.doctor)
|
|
427
|
+
await refresh()
|
|
428
|
+
toast(payload.report?.message || '\u8FD0\u884C\u73AF\u5883\u51C6\u5907\u5B8C\u6210', Boolean(payload.report?.ok))
|
|
429
|
+
}
|
|
430
|
+
$('connectButton').onclick = async () => {
|
|
431
|
+
const payload = await fetchJson('/api/connect', { method: 'POST' })
|
|
432
|
+
await refresh()
|
|
433
|
+
toast(payload.message || '\u6B63\u5728\u8FDE\u63A5\u63A7\u5236\u9762', true)
|
|
434
|
+
}
|
|
435
|
+
$('disconnectButton').onclick = async () => {
|
|
436
|
+
const payload = await fetchJson('/api/disconnect', { method: 'POST' })
|
|
437
|
+
await refresh()
|
|
438
|
+
toast(payload.message || '\u5DF2\u65AD\u5F00\u63A7\u5236\u9762\u8FDE\u63A5', true)
|
|
439
|
+
}
|
|
440
|
+
$('saveConfigButton').onclick = async () => {
|
|
441
|
+
const next = {
|
|
442
|
+
...(state.config || {}),
|
|
443
|
+
cloudUrl: $('cloudUrl').value.trim(),
|
|
444
|
+
workspaceRoot: $('workspaceRoot').value.trim(),
|
|
445
|
+
maxConcurrency: Number($('maxConcurrency').value || '1'),
|
|
446
|
+
labels: fromLines($('labels').value),
|
|
447
|
+
capabilities: fromLines($('capabilities').value),
|
|
448
|
+
projectBindings: getBindingsFromTable(),
|
|
449
|
+
}
|
|
450
|
+
const payload = await fetchJson('/api/config', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(next) })
|
|
451
|
+
state.config = payload.config
|
|
452
|
+
fillConfig(); renderBindings(); renderStats(); toast('\u914D\u7F6E\u5DF2\u4FDD\u5B58')
|
|
453
|
+
}
|
|
454
|
+
$('pairButton').onclick = async () => {
|
|
455
|
+
const payload = await fetchJson('/api/pair', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ pairingCode: $('pairingCode').value.trim(), name: $('pairingName').value.trim() }) })
|
|
456
|
+
state.config = payload.config
|
|
457
|
+
await refresh()
|
|
458
|
+
toast(payload.message || '\u914D\u5BF9\u8FDE\u63A5\u6210\u529F')
|
|
459
|
+
}
|
|
460
|
+
$('resetButton').onclick = async () => {
|
|
461
|
+
await fetchJson('/api/reset', { method: 'POST' })
|
|
462
|
+
await refresh()
|
|
463
|
+
toast('\u672C\u5730\u914D\u7F6E\u5DF2\u6E05\u7A7A')
|
|
464
|
+
}
|
|
465
|
+
$('addBindingButton').onclick = () => {
|
|
466
|
+
state.config = state.config || {}
|
|
467
|
+
state.config.projectBindings = [...(state.config.projectBindings || []), { projectId: '', repoUrl: '', localPath: '' }]
|
|
468
|
+
renderBindings()
|
|
469
|
+
}
|
|
470
|
+
$('saveBindingsButton').onclick = async () => {
|
|
471
|
+
const next = { ...(state.config || {}), projectBindings: getBindingsFromTable() }
|
|
472
|
+
const payload = await fetchJson('/api/config', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(next) })
|
|
473
|
+
state.config = payload.config
|
|
474
|
+
renderBindings(); toast('Project bindings \u5DF2\u4FDD\u5B58')
|
|
475
|
+
}
|
|
476
|
+
document.addEventListener('click', async (event) => {
|
|
477
|
+
const target = event.target
|
|
478
|
+
if (!(target instanceof HTMLElement)) return
|
|
479
|
+
const index = target.getAttribute('data-remove')
|
|
480
|
+
if (index === null) return
|
|
481
|
+
state.config.projectBindings.splice(Number(index), 1)
|
|
482
|
+
renderBindings()
|
|
483
|
+
})
|
|
484
|
+
refresh().then(async () => {
|
|
485
|
+
const payload = await fetchJson('/api/doctor')
|
|
486
|
+
renderDoctor(payload)
|
|
487
|
+
}).catch((error) => { $('doctorOutput').textContent = String(error); toast(String(error), false) })
|
|
488
|
+
setInterval(() => { refresh().catch(() => undefined) }, 5000)
|
|
489
|
+
</script>
|
|
490
|
+
</body>
|
|
491
|
+
</html>`,Se=()=>{let e=g(),t=fr.createServer(async(r,o)=>{try{let n=hr(r);if(r.method==="GET"&&n.pathname==="/"){o.statusCode=200,o.setHeader("Content-Type","text/html; charset=utf-8"),o.end(yr());return}if(r.method==="GET"&&(n.pathname==="/health"||n.pathname==="/api/health")){b(o,200,kr());return}if(r.method==="GET"&&n.pathname==="/api/config"){b(o,200,{config:g()});return}if(r.method==="GET"&&n.pathname==="/api/status"){b(o,200,{runtime:T()});return}if(r.method==="GET"&&n.pathname==="/api/doctor"){b(o,200,await ee());return}if(r.method==="GET"&&n.pathname==="/api/update"){b(o,200,await Z());return}if(r.method==="POST"&&n.pathname==="/api/bootstrap-runtime"){let s=await W({autoInstall:!0});b(o,200,{report:s,doctor:await ee()});return}if(r.method==="PUT"&&n.pathname==="/api/config"){let s=await ot(r),i={...g(),...s};H(i),y({config:i,paired:!!(i.executorId&&i.executorToken),executorId:i.executorId}),b(o,200,{config:i,message:"saved"});return}if(r.method==="POST"&&n.pathname==="/api/pair"){let s=await ot(r),i=g(),a=await Fe({pairingCode:s.pairingCode,machineId:i.machineId,machineName:i.machineName,name:s.name?.trim()||`worker-${process.pid}`,workspaceRoot:i.workspaceRoot,maxConcurrency:i.maxConcurrency,labels:i.labels,capabilities:i.capabilities,platform:process.platform,version:_()},i.cloudUrl),c={...i,executorName:s.name?.trim()||`worker-${process.pid}`,executorId:a.executorId,executorToken:a.executorToken};H(c),y({paired:!0,executorId:a.executorId,config:c,daemonMode:"starting"}),ve(),b(o,200,{config:c,executor:a.executor,message:"\u914D\u5BF9\u5B8C\u6210\uFF0C\u6B63\u5728\u8FDE\u63A5\u63A7\u5236\u9762"});return}if(r.method==="POST"&&n.pathname==="/api/reset"){Q(),y({daemonMode:"idle",paired:!1,connected:!1,executorId:void 0,config:g(),runningTaskIds:[],queuedTaskIds:[],lastError:void 0}),b(o,200,{config:g(),message:"reset"});return}if(r.method==="POST"&&n.pathname==="/api/disconnect"){nt(),b(o,200,{runtime:T(),message:"\u5DF2\u65AD\u5F00\u63A7\u5236\u9762\u8FDE\u63A5"});return}if(r.method==="POST"&&n.pathname==="/api/connect"){ve(),b(o,200,{runtime:T(),message:"\u5DF2\u53D1\u8D77\u63A7\u5236\u9762\u8FDE\u63A5"});return}if(r.method==="POST"&&n.pathname==="/api/update"){let s=await z();b(o,s.applied?202:200,s),s.applied&&setTimeout(()=>process.exit(0),250);return}b(o,404,{message:"not found"})}catch(n){b(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 setup page http://127.0.0.1:${e.localServerPort}`)}),t};import{mkdirSync as br}from"node:fs";import F from"node:path";var te=e=>{let t={root:e,reposDir:F.join(e,"repos"),worktreesDir:F.join(e,"worktrees"),artifactsDir:F.join(e,"artifacts"),cacheDir:F.join(e,"cache")};for(let r of Object.values(t))br(r,{recursive:!0});return t},it=(e,t)=>F.join(e,"worktrees",t);import{accessSync as xr,constants as wr,mkdirSync as vr}from"node:fs";var Sr=e=>{try{return vr(e,{recursive:!0}),xr(e,wr.W_OK),{ok:!0,detail:`\u53EF\u5199: ${e}`}}catch(t){return{ok:!1,detail:t instanceof Error?t.message:"path access failed"}}},Tr=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"}}},Cr=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"}}},Ir=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"}}},Rr=e=>{let t=e.filter(o=>o.ok).length,r=e.length-t;return{total:e.length,passed:t,failed:r,ok:r===0}},st=async()=>{let e=g(),[t,r,o]=await Promise.all([Cr(e.cloudUrl),Ir(),W()]),n=Sr(M()),s=Tr(e.workspaceRoot),i=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:i.ok,detail:i.detail,hint:i.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:s.ok,detail:s.detail,hint:s.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:i.ok,opencodeAvailable:a.ok,workerHomeWritable:n.ok,workspaceConfigured:!!e.workspaceRoot,workspaceReady:s.ok,machineIdConfigured:!!e.machineId,paired:!!(e.executorId&&e.executorToken),cloudUrlConfigured:!!e.cloudUrl?.trim(),cloudReachable:t.ok,officialSiteReachable:r.ok},items:c,summary:Rr(c),cloudProbe:t,officialSiteProbe:r,runtime:T()}},Te=async()=>{console.log(JSON.stringify(await st(),null,2))},ee=()=>st();import{readdir as Pr,stat as Ce}from"node:fs/promises";import at from"node:os";import N from"node:path";import{simpleGit as oe}from"simple-git";var $="main",Er=e=>{let t=e.trim();return t?t==="~"?at.homedir():t.startsWith("~/")?N.join(at.homedir(),t.slice(2)):t:""},re=e=>N.resolve(Er(e)),Wr=(e,t)=>{let r=N.relative(e,t);return r===""||!r.startsWith("..")&&!N.isAbsolute(r)},Br=(e,t)=>{let r=re(e),o=t?.trim()?re(t):r;return{resolvedRootPath:r,requestedPath:o}},C=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},Dr=async e=>{let t=await oe(e).branch(["-a"]);return new Set(t.all.map(C).filter(Boolean)).size},Mr=async(e,t)=>{let r=oe(e),[o,n]=await Promise.all([r.branch(["-a"]),r.branchLocal()]),s="";try{s=C(await r.raw(["symbolic-ref","--quiet","--short","refs/remotes/origin/HEAD"]))}catch{s=""}let i=Array.from(new Set(o.all.map(C).filter(Boolean))).sort((u,p)=>u.localeCompare(p)),a=C(t??""),c=C(n.current),l=[s,a,c,i[0],$].find(u=>u&&(u===$||i.includes(u)))??$;return{branches:i,defaultBranch:l}},ct=async e=>{let t=re(e);try{if(!(await Ce(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=N.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 i=(await o.getRemotes(!0)).find(c=>c.name==="origin"),a=await Dr(t);return{ok:!0,path:t,name:r,gitUrl:i?.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"}}},dt=async(e,t)=>{let{resolvedRootPath:r,requestedPath:o}=Br(e,t);if(!Wr(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 Ce(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 s=(await Pr(o,{withFileTypes:!0})).filter(i=>i.isDirectory()).map(i=>({name:i.name,path:N.join(o,i.name)})).sort((i,a)=>i.name.localeCompare(a.name,"zh-Hans-CN"));return{ok:!0,path:o,rootPath:r,parentPath:o===r?void 0:N.dirname(o),entries:s,message:s.length>0?`\u5171\u627E\u5230 ${s.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"}}},lt=async(e,t)=>{let r=re(e);try{if(!(await Ce(r)).isDirectory())return{ok:!1,branches:[],defaultBranch:C(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:C(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:C(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 s=await Mr(r,t);return{ok:!0,branches:s.branches,defaultBranch:s.defaultBranch}}catch(o){return{ok:!1,branches:[],defaultBranch:C(t??"")||$,message:o instanceof Error?o.message:"\u8BFB\u53D6\u5206\u652F\u5217\u8868\u5931\u8D25\u3002"}}};var Ar=e=>e.trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^-+|-+$/g,"")||"task",V=e=>`task/${Ar(e)}`;var Or=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."},$r=e=>{let t=Or(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(`
|
|
492
|
+
`)},Nr=(e,t)=>{let r=(e?.trim()||t?.trim()||"Worker delivery update").replace(/\s+/g," ");return r.length>72?`${r.slice(0,69)}...`:r},Ur=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||V(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||V(e.taskId),o=e.filesChanged.length>0&&!!e.commitShas?.length;t.pullRequest={ready:o,remoteReady:!!e.remoteBranchName,repoUrl:e.repoUrl,title:o?Nr(e.taskTitle,e.taskDescription):void 0,description:o?$r({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},J=(e,t)=>({...e,delivery:Ur({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 to}from"node:child_process";import{createHash as ro}from"node:crypto";import{existsSync as oo,mkdirSync as kt,rmSync as no}from"node:fs";import se from"node:path";import{simpleGit as ae}from"simple-git";import{mkdirSync as _r,rmSync as Lr,writeFileSync as ut}from"node:fs";import jr from"node:os";import Ie from"node:path";import{spawnSync as Hr}from"node:child_process";var K=(e,t)=>{let r=Hr("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{K(e,t)}catch{return}},pt=e=>{let t=Ie.join(jr.tmpdir(),"vibemux-git",e.taskId);_r(t,{recursive:!0}),e.identity.name&&e.identity.email&&(K(e.worktreePath,["config","--local","user.name",e.identity.name]),K(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=Ie.join(t,"id_ed25519");ut(o,e.identity.credentialToken,{encoding:"utf8",mode:384}),K(e.worktreePath,["config","--local","core.sshCommand",`ssh -i "${o}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null`])}else if(r){let o=Ie.join(t,"credentials");ut(o,`${r}
|
|
493
|
+
`,"utf8"),K(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"]),Lr(t,{recursive:!0,force:!0})}}};import{existsSync as Gr}from"node:fs";import{createOpencode as zr,createOpencodeClient as Fr}from"@opencode-ai/sdk";var mt=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 j=null,Re="",ie=null,Vr=e=>{if(!e)return;let[t,...r]=e.split("/"),o=r.join("/");if(!(!t||!o))return{providerID:t,modelID:o}},Jr=async e=>{try{return(await zr({port:4096,timeout:15e3,config:mt(e)})).server}catch{return{url:"http://127.0.0.1:4096",close:()=>{}}}},Kr=async()=>{let e=g().opencodeConfigContent?.trim()??"";return j&&Re!==e&&(ie?.close(),ie=null,j=null),j||(Re=e,j=Jr(e).then(t=>(ie=t,t)).catch(t=>{throw j=null,Re="",ie=null,t})),j},qr=async e=>{let t=await Kr();return Fr({baseUrl:t.url,directory:e})},Qr=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},Xr=async e=>{if(!Gr(e.cwd))throw new Error(`\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u5B58\u5728: ${e.cwd}`);let t=await qr(e.cwd),r=await Qr(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 s=await t.session.prompt({path:{id:r},query:{directory:e.cwd},body:{model:Vr(e.executionModel),parts:[{type:"text",text:e.prompt}]}});if(o||e.signal?.aborted)throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");if(!s.data)throw new Error("OpenCode \u8C03\u7528\u5931\u8D25");let i=s.data.parts.filter(a=>a.type==="text").map(a=>a.text?.trim()??"").filter(Boolean).join(`
|
|
494
|
+
|
|
495
|
+
`);return{sessionId:r,output:i||"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(`
|
|
496
|
+
`);return Xr({cwd:e.cwd,title:`Task: ${e.title}`,prompt:t,executionModel:e.executionModel,signal:e.signal})};import{simpleGit as Yr}from"simple-git";var Zr=async e=>{let t=await e.status();return Array.from(new Set(t.files.map(r=>r.path))).sort()},eo=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}},ft=async(e,t,r)=>{let o=Yr(t),n=await Zr(o),s=await eo(o,e);return{filesChanged:n,commitShas:void 0,patchArtifact:s,remoteBranchName:e.returnMode==="branch"?r:void 0}};var io="main",so=["&&","||","|",";",">","<","$(","`"],ao=e=>ro("sha1").update(e).digest("hex").slice(0,12),co=e=>e.replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"")||"repo",lo=(e,t)=>{let r=se.basename(t).replace(/\.git$/i,"");return se.join(e,"repos",`${co(r)}-${ao(t)}`)},uo=e=>e.baseCommit?.trim()||e.defaultBranch?.trim()||io,Pe=e=>`origin/${e}`,yt=async(e,t)=>(await e.branch(["-r"])).all.includes(Pe(t)),po=async(e,t)=>{await e.fetch(["--all","--prune"]),await yt(e,t)&&await e.rebase([Pe(t)])},mo=e=>e.gitIdentity??{mode:e.gitIdentityMode??"personal"},go=(e,t=1600)=>{let r=e.trim();return r?r.length>t?`${r.slice(0,t)}
|
|
497
|
+
...\uFF08\u5DF2\u622A\u65AD\uFF09`:r:""},fo=e=>{let t=[],r="",o=null,n=!1;for(let s of e){if(n){r+=s,n=!1;continue}if(s==="\\"){n=!0;continue}if(o){s===o?o=null:r+=s;continue}if(s==='"'||s==="'"){o=s;continue}if(/\s/.test(s)){r&&(t.push(r),r="");continue}r+=s}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},ho=e=>{let t=e.trim();if(!t)throw new Error("\u547D\u4EE4\u9884\u8BBE\u4E0D\u80FD\u4E3A\u7A7A\u3002");if(so.some(s=>t.includes(s)))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=fo(t),o={};for(;r.length>0&&/^[A-Za-z_][A-Za-z0-9_]*=.*/.test(r[0]);){let s=r.shift(),i=s.indexOf("=");o[s.slice(0,i)]=s.slice(i+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}},ko=(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},yo=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):[],bo=e=>{let t=yo(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(`
|
|
498
|
+
`)},xo=async e=>{let t=ho(e.commandText);return await new Promise((r,o)=>{let n=to(t.command,t.args,{cwd:e.cwd,env:{...process.env,...t.env},stdio:["ignore","pipe","pipe"]}),s="",i="",a=!1,c=()=>{a=!0,n.kill("SIGTERM")};e.signal?.addEventListener("abort",c,{once:!0}),n.stdout.on("data",l=>{s+=l.toString()}),n.stderr.on("data",l=>{i+=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=go([s,i].filter(Boolean).join(`
|
|
499
|
+
`));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(`
|
|
500
|
+
|
|
501
|
+
`)))})})},ht=async e=>{let t=ko(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 xo({cwd:e.cwd,label:r,commandText:t,signal:e.signal});return e.emit("executing",o.output?`${r} \u5DF2\u5B8C\u6210\u3002
|
|
502
|
+
${o.output}`:`${r} \u5DF2\u5B8C\u6210\u3002`),`${r}\uFF1A${t}`},wo=(e,t)=>e?.find(r=>!!(r.projectId&&r.projectId===t.projectId||r.repoUrl&&r.repoUrl===t.repoUrl))?.localPath,vo=async(e,t,r)=>{let o=wo(r,t)||lo(e,t.repoUrl);kt(se.dirname(o),{recursive:!0}),oo(o)||await ae().clone(t.repoUrl,o);let n=ae(o);return await n.fetch(["--all","--prune"]),{repoDir:o,git:n}},So=async(e,t)=>{let r=ae(e);try{await r.raw(["worktree","remove","--force",t])}catch{}no(t,{recursive:!0,force:!0}),kt(se.dirname(t),{recursive:!0})},To=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(),s,i="\u5DF2\u521B\u5EFA\u672C\u5730\u63D0\u4EA4\u3002";return e.identity.credentialToken?(await po(t,e.branchName),await t.push(["-u","origin",e.branchName]),s=e.branchName,i=`\u5DF2\u63A8\u9001\u8FDC\u7AEF\u5206\u652F ${e.branchName}\u3002`):(e.task.returnMode==="branch"||e.task.returnMode==="commit")&&(i="\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:s,pushMessage:i}},bt=async e=>{let t=new Date().toISOString(),r=V(e.task.id),o=it(e.workspaceRoot,e.task.id),n=null;try{e.emit("preparing","\u68C0\u67E5 Worker \u8FD0\u884C\u73AF\u5883");let s=await W({autoInstall:!0});if(!s.ok)throw new Error(s.message);e.emit("preparing",s.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:i,git:a}=await vo(e.workspaceRoot,e.task,e.projectBindings);e.emit("preparing",`\u4ED3\u5E93\u5DF2\u5C31\u7EEA\uFF1A${i}`),await So(i,o);let c=await yt(a,r)?Pe(r):uo(e.task);await a.raw(["worktree","add","--force","-B",r,o,c]);let l=mo(e.task);n=pt({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:bo(e.task),signal:e.signal});for(let v of["build","test","lint"]){let $e=await ht({step:v,task:e.task,cwd:o,signal:e.signal,emit:e.emit});$e&&u.push($e)}let d=await To({task:e.task,worktreePath:o,branchName:r,identity:l}),m=await ft(e.task,o,r),k;m.patchArtifact&&e.executorToken&&(e.emit("syncing_back","\u6B63\u5728\u4E0A\u4F20 patch \u4EA7\u7269\u5230\u63A7\u5236\u9762"),k=(await Je({cloudUrl:e.cloudUrl,executorToken:e.executorToken,taskId:e.task.id,filename:m.patchArtifact.filename,content:m.patchArtifact.content})).artifactId);let w=new Date().toISOString(),h=J({taskId:e.task.id,status:"completed",returnMode:e.task.returnMode,summary:[u.length>0?`\u5DF2\u6267\u884C\u9879\u76EE\u9884\u8BBE\u547D\u4EE4\uFF1A
|
|
503
|
+
${u.map(v=>`- ${v}`).join(`
|
|
504
|
+
`)}`:void 0,x.output,d.pushMessage].filter(Boolean).join(`
|
|
505
|
+
|
|
506
|
+
`),filesChanged:d.changedFiles.length>0?d.changedFiles:m.filesChanged,remoteBranchName:d.remoteBranchName,commitShas:d.commitShas??m.commitShas,patchArtifactId:k,startedAt:t,completedAt:w,durationSec:Math.max(0,Math.round((new Date(w).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:w,updatedAt:w,result:h}}}catch(s){let i=new Date().toISOString(),a=s instanceof Error?s.message:"worker task execution failed";return{task:{...e.task,status:"failed",startedAt:t,completedAt:i,updatedAt:i,errorMessage:a,result:J({taskId:e.task.id,status:"failed",returnMode:e.task.returnMode,summary:a,filesChanged:[],startedAt:t,completedAt:i,durationSec:Math.max(0,Math.round((new Date(i).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 Ee=new Map,We=e=>{let t=Ee.get(e);if(t){for(let r of t)clearTimeout(r);Ee.delete(e)}},xt=(e,t,r)=>{let o=new Date().toISOString();return{...e,status:"cancelled",executorNodeId:t,completedAt:o,updatedAt:o,result:J({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})}},wt=We,vt=e=>{let{task:t,executorId:r,workspaceRoot:o,projectBindings:n,cloudUrl:s,executorToken:i,connection:a}=e,c=new AbortController,l=[setTimeout(()=>{e.onStart(t.id),bt({task:t,executorId:r,workspaceRoot:o,projectBindings:n,signal:c.signal,cloudUrl:s,executorToken:i,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}),We(t.id))})},20)];return Ee.set(t.id,l),{abort(){c.abort(),e.onFinish(t.id),We(t.id)}}};var U=(e,t)=>{console.log("[terminal][worker]",e,t??{})},Co=e=>{let{connection:t,executorId:r,sessionId:o,stream:n,chunk:s}=e;t.send({type:"executor.terminal.session.output",executorId:r,output:{sessionId:o,stream:n,chunk:s,at:new Date().toISOString()}})},St=e=>{let t=g(),r={...t,opencodeConfigContent:e.opencodeConfigContent??"",defaultModel:e.defaultModel??"",maxConcurrency:Math.max(1,e.maxConcurrency??t.maxConcurrency)};return H(r),y({config:r}),r},Tt=e=>t=>{if(e.getCurrentSocket()!==e.expectedSocket)return;let r=e.getConfig(),o=e.getConnection();if(t.type==="control-plane.ready"){r=St({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=St({opencodeConfigContent:t.opencodeConfigContent,defaultModel:t.defaultModel,maxConcurrency:t.maxConcurrency}),e.setConfig(r),y({config:r});return}if(t.type==="config.export.request"){r=g(),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=g(),e.setConfig(r),ct(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=g(),e.setConfig(r),dt(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=g(),e.setConfig(r),lt(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=g(),e.setConfig(r),U("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=g(),e.setConfig(r);let n=t.cwd||r.workspaceRoot;U("received terminal session open",{sessionId:t.sessionId,cwd:n});let s=e.openTerminalSession({cwd:n,onExit:i=>{e.terminalSessions.delete(t.sessionId),o?.send({type:"executor.terminal.session.exit",executorId:r.executorId,sessionId:t.sessionId,exitCode:i,at:new Date().toISOString()})},onLog:(i,a)=>{U(i,{sessionId:t.sessionId,...a})},onOutput:(i,a)=>{Co({connection:o,executorId:r.executorId,sessionId:t.sessionId,stream:i,chunk:a})},onReady:(i,a)=>{o?.send({type:"executor.terminal.session.ready",executorId:r.executorId,sessionId:t.sessionId,cwd:n,mode:i,at:new Date().toISOString()}),U("terminal session ready",{sessionId:t.sessionId,cwd:n,mode:i,backend:a})}});e.terminalSessions.set(t.sessionId,s);return}if(t.type==="executor.terminal.session.input"){U("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"){U("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"){U("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(),s=e.getQueuedTaskIds();if(e.assignedTasks.has(t.task.id)||n.includes(t.task.id)||s.includes(t.task.id)){e.assignedTasks.set(t.task.id,t.task);return}e.assignedTasks.set(t.task.id,t.task),e.setQueuedTaskIds([...s,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),wt(t.taskId),e.assignedTasks.delete(t.taskId),e.setQueuedTaskIds(e.getQueuedTaskIds().filter(s=>s!==t.taskId)),e.setRunningTaskIds(e.getRunningTaskIds().filter(s=>s!==t.taskId)),e.syncRuntimeState(),e.drainAssignedQueue(),n&&o?.send({type:"task.result",executorId:r.executorId,task:xt(n,r.executorId,t.reason)})}};import{existsSync as Be,mkdirSync as Io,readFileSync as Ct}from"node:fs";import{spawnSync as Ro}from"node:child_process";import Po from"node:os";import ce from"node:path";var It=ce.join(Po.homedir(),".ssh"),Eo=["id_ed25519.pub","id_rsa.pub","id_ecdsa.pub"],Wo="vibemux_worker_ed25519",Bo=e=>{Io(ce.dirname(e),{recursive:!0});let t=Ro("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())},De=()=>{try{for(let r of Eo){let o=ce.join(It,r);if(Be(o))return Ct(o,"utf8").trim()}let e=ce.join(It,Wo),t=`${e}.pub`;return Be(t)||Bo(e),Be(t)?Ct(t,"utf8").trim():void 0}catch{return}};import{spawn as Me,spawnSync as Do}from"node:child_process";import{accessSync as Mo,constants as Ao}from"node:fs";import{StringDecoder as Rt}from"node:string_decoder";import Oo from"node-pty";var Et=120,Wt=32,Ae="xterm-256color",$o=250,No=String.raw`
|
|
507
|
+
import base64
|
|
508
|
+
import errno
|
|
509
|
+
import fcntl
|
|
510
|
+
import json
|
|
511
|
+
import os
|
|
512
|
+
import pty
|
|
513
|
+
import select
|
|
514
|
+
import signal
|
|
515
|
+
import struct
|
|
516
|
+
import sys
|
|
517
|
+
import termios
|
|
518
|
+
import threading
|
|
519
|
+
|
|
520
|
+
shell = os.environ.get('DEVKANBAN_SHELL') or '/bin/sh'
|
|
521
|
+
cwd = os.environ.get('DEVKANBAN_CWD') or os.getcwd()
|
|
522
|
+
cols = max(1, int(os.environ.get('DEVKANBAN_COLS') or '120'))
|
|
523
|
+
rows = max(1, int(os.environ.get('DEVKANBAN_ROWS') or '32'))
|
|
524
|
+
term_name = os.environ.get('DEVKANBAN_TERM_NAME') or 'xterm-256color'
|
|
525
|
+
|
|
526
|
+
def set_winsize(fd, cols, rows):
|
|
527
|
+
fcntl.ioctl(fd, termios.TIOCSWINSZ, struct.pack('HHHH', rows, cols, 0, 0))
|
|
528
|
+
|
|
529
|
+
pid, master_fd = pty.fork()
|
|
530
|
+
|
|
531
|
+
if pid == 0:
|
|
532
|
+
try:
|
|
533
|
+
os.chdir(cwd)
|
|
534
|
+
except OSError as error:
|
|
535
|
+
sys.stderr.write(f'[python-pty] chdir failed: {error}\n')
|
|
536
|
+
sys.stderr.flush()
|
|
537
|
+
os._exit(1)
|
|
538
|
+
|
|
539
|
+
os.environ['TERM'] = term_name
|
|
540
|
+
|
|
541
|
+
try:
|
|
542
|
+
os.execvpe(shell, [shell, '-i', '-l'], os.environ)
|
|
543
|
+
except OSError as error:
|
|
544
|
+
sys.stderr.write(f'[python-pty] exec failed: {error}\n')
|
|
545
|
+
sys.stderr.flush()
|
|
546
|
+
os._exit(1)
|
|
547
|
+
|
|
548
|
+
set_winsize(master_fd, cols, rows)
|
|
549
|
+
closed = False
|
|
550
|
+
|
|
551
|
+
def terminate_child():
|
|
552
|
+
global closed
|
|
553
|
+
if closed:
|
|
554
|
+
return
|
|
555
|
+
|
|
556
|
+
closed = True
|
|
557
|
+
|
|
558
|
+
try:
|
|
559
|
+
os.kill(pid, signal.SIGTERM)
|
|
560
|
+
except ProcessLookupError:
|
|
561
|
+
return
|
|
562
|
+
|
|
563
|
+
def handle_control_message(message):
|
|
564
|
+
msg_type = message.get('type')
|
|
565
|
+
|
|
566
|
+
if msg_type == 'input':
|
|
567
|
+
data = base64.b64decode(message.get('data') or '')
|
|
568
|
+
if data:
|
|
569
|
+
os.write(master_fd, data)
|
|
570
|
+
return
|
|
571
|
+
|
|
572
|
+
if msg_type == 'resize':
|
|
573
|
+
next_cols = max(1, int(message.get('cols') or cols))
|
|
574
|
+
next_rows = max(1, int(message.get('rows') or rows))
|
|
575
|
+
set_winsize(master_fd, next_cols, next_rows)
|
|
576
|
+
try:
|
|
577
|
+
os.kill(pid, signal.SIGWINCH)
|
|
578
|
+
except ProcessLookupError:
|
|
579
|
+
pass
|
|
580
|
+
return
|
|
581
|
+
|
|
582
|
+
if msg_type == 'close':
|
|
583
|
+
terminate_child()
|
|
584
|
+
|
|
585
|
+
def control_loop():
|
|
586
|
+
try:
|
|
587
|
+
for line in sys.stdin:
|
|
588
|
+
text = line.strip()
|
|
589
|
+
if not text:
|
|
590
|
+
continue
|
|
591
|
+
|
|
592
|
+
try:
|
|
593
|
+
handle_control_message(json.loads(text))
|
|
594
|
+
except Exception as error:
|
|
595
|
+
sys.stderr.write(f'[python-pty] control error: {error}\n')
|
|
596
|
+
sys.stderr.flush()
|
|
597
|
+
finally:
|
|
598
|
+
terminate_child()
|
|
599
|
+
|
|
600
|
+
threading.Thread(target=control_loop, daemon=True).start()
|
|
601
|
+
|
|
602
|
+
exit_code = 0
|
|
603
|
+
|
|
604
|
+
while True:
|
|
605
|
+
try:
|
|
606
|
+
readable, _, _ = select.select([master_fd], [], [], 0.2)
|
|
607
|
+
except (OSError, ValueError):
|
|
608
|
+
break
|
|
609
|
+
|
|
610
|
+
if master_fd in readable:
|
|
611
|
+
try:
|
|
612
|
+
chunk = os.read(master_fd, 65536)
|
|
613
|
+
except OSError as error:
|
|
614
|
+
if error.errno == errno.EIO:
|
|
615
|
+
break
|
|
616
|
+
raise
|
|
617
|
+
|
|
618
|
+
if not chunk:
|
|
619
|
+
break
|
|
620
|
+
|
|
621
|
+
os.write(sys.stdout.fileno(), chunk)
|
|
622
|
+
sys.stdout.flush()
|
|
623
|
+
|
|
624
|
+
try:
|
|
625
|
+
result = os.waitpid(pid, os.WNOHANG)
|
|
626
|
+
except ChildProcessError:
|
|
627
|
+
break
|
|
628
|
+
|
|
629
|
+
if result != (0, 0):
|
|
630
|
+
status = result[1]
|
|
631
|
+
|
|
632
|
+
if os.WIFEXITED(status):
|
|
633
|
+
exit_code = os.WEXITSTATUS(status)
|
|
634
|
+
elif os.WIFSIGNALED(status):
|
|
635
|
+
exit_code = 128 + os.WTERMSIG(status)
|
|
636
|
+
|
|
637
|
+
break
|
|
638
|
+
|
|
639
|
+
try:
|
|
640
|
+
os.close(master_fd)
|
|
641
|
+
except OSError:
|
|
642
|
+
pass
|
|
643
|
+
|
|
644
|
+
sys.exit(exit_code)
|
|
645
|
+
`,B,Uo=["-i","-l"],de=e=>{if(!e?.trim())return!1;try{return Mo(e,Ao.X_OK),!0}catch{return!1}},Pt=e=>{let t=e.end();return t.length>0?t:""},Oe=(e,t,r)=>{let o=!1,n=setTimeout(()=>{o||(o=!0,e(t,r))},$o);return{markReady:()=>{o||(o=!0,clearTimeout(n),e(t,r))},dispose:()=>{clearTimeout(n)}}},_o=()=>{if(B!==void 0)return B;let e=["/usr/bin/python3","/opt/homebrew/bin/python3"];for(let r of e)if(de(r))return B=r,B;let t=["python3","python"];for(let r of t){let o=Do(r,["-c","import sys"],{stdio:"ignore"});if(!o.error&&o.status===0)return B=r,B}return B=null,B},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"},Bt=(e,t)=>new Promise(r=>{let o=le(),n=Me(o,["-l","-c",e],{cwd:t?.trim()||process.cwd(),env:process.env}),s="",i="";n.stdout.on("data",a=>{s+=a.toString()}),n.stderr.on("data",a=>{i+=a.toString()}),n.on("close",a=>{r({command:e,cwd:t,stdout:s,stderr:i,exitCode:a??0,at:new Date().toISOString()})}),n.on("error",a=>{r({command:e,cwd:t,stdout:s,stderr:`${i}${a.message}`,exitCode:1,at:new Date().toISOString()})})}),Lo=e=>{let{cols:t=Et,cwd:r,onExit:o,onLog:n,onOutput:s,onReady:i,rows:a=Wt,shell:c=le()}=e,l=Oo.spawn(c,Uo,{cwd:r,env:process.env,cols:t,rows:a,name:Ae}),u=Oe(i,"pty","node-pty");return l.onData(p=>{u.markReady(),n("pty output",{backend:"node-pty",chunkLength:p.length,preview:p.slice(0,120)}),s("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")}},jo=e=>{let{cols:t=Et,cwd:r,onExit:o,onLog:n,onOutput:s,onReady:i,rows:a=Wt,shell:c=le()}=e,l=_o();if(!l)throw new Error("python3 \u4E0D\u53EF\u7528\uFF0C\u65E0\u6CD5\u542F\u52A8 PTY fallback\u3002");let u=Me(l,["-u","-c",No],{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:Ae},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 Rt("utf8"),d=new Rt("utf8"),m=Oe(i,"pty","python-pty"),k=(h,v)=>{v.length!==0&&(h==="stdout"&&m.markReady(),n("pty output",{backend:"python-pty",chunkLength:v.length,preview:v.slice(0,120),stream:h}),s(h,v))};u.stdout.on("data",h=>{k("stdout",x.write(h))}),u.stderr.on("data",h=>{k("system",d.write(h))}),u.on("error",h=>{n("python pty error",{message:h.message}),s("system",`\r
|
|
646
|
+
[python-pty error] ${h.message}\r
|
|
647
|
+
`)}),u.on("close",h=>{m.dispose(),k("stdout",Pt(x)),k("system",Pt(d)),n("pty exit",{backend:"python-pty",exitCode:h??0}),o(h??0)});let w=h=>{p.destroyed||p.write(`${JSON.stringify(h)}
|
|
648
|
+
`)};return{backend:"python-pty",mode:"pty",write:h=>{w({type:"input",data:Buffer.from(h,"utf8").toString("base64")})},resize:(h,v)=>{w({type:"resize",cols:Math.max(1,h),rows:Math.max(1,v)})},kill:()=>{w({type:"close"}),p.end(),u.kill("SIGTERM")}}},Ho=e=>{let{cwd:t,onExit:r,onLog:o,onOutput:n,onReady:s}=e,i=Me("/bin/bash",["-il"],{cwd:t,env:{...process.env,BASH_SILENCE_DEPRECATION_WARNING:"1",TERM:process.env.TERM||Ae},stdio:"pipe"});i.stdin.setDefaultEncoding("utf8"),i.stdout.setEncoding("utf8"),i.stderr.setEncoding("utf8");let a=Oe(s,"pipe","pipe");return i.stdout.on("data",c=>{a.markReady(),o("fallback shell stdout",{backend:"pipe",chunkLength:c.length,preview:c.slice(0,120)}),n("stdout",c)}),i.stderr.on("data",c=>{a.markReady(),o("fallback shell stderr",{backend:"pipe",chunkLength:c.length,preview:c.slice(0,120)}),n("stderr",c)}),i.on("error",c=>{o("fallback shell error",{message:c.message}),n("system",`\r
|
|
649
|
+
[fallback shell error] ${c.message}\r
|
|
650
|
+
`)}),i.on("close",c=>{a.dispose(),o("fallback shell exit",{backend:"pipe",exitCode:c??0}),r(c??0)}),i.stdin.write(`export PS1="$ "
|
|
651
|
+
`),{backend:"pipe",mode:"pipe",write:c=>{i.stdin.destroyed||i.stdin.write(c.replace(/\r/g,`
|
|
652
|
+
`))},resize:()=>{},kill:()=>i.kill("SIGTERM")}},Dt=e=>{let{cwd:t,onLog:r}=e,o=e.shell??le();r("opening pty session",{cwd:t,shell:o});try{return Lo({...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 jo({...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 Ho(e)};var f={connection:null,reconnectTimer:null,stopped:!1,connect:null},Go=new Map,Mt=()=>{Se()};var nt=()=>{f.stopped=!0,f.reconnectTimer&&(clearTimeout(f.reconnectTimer),f.reconnectTimer=null),f.connection?.socket?.close(1e3,"manual disconnect"),f.connection=null,y({daemonMode:"disconnected",connected:!1,lastDisconnectAt:new Date().toISOString(),lastError:"\u5DF2\u624B\u52A8\u65AD\u5F00\u63A7\u5236\u9762\u8FDE\u63A5"})},ve=()=>{let e=g();if(!e.executorId||!e.executorToken)throw new Error("\u5F53\u524D worker \u5C1A\u672A\u5B8C\u6210 pairing\uFF0C\u65E0\u6CD5\u8FDE\u63A5\u63A7\u5236\u9762");f.stopped=!1,f.reconnectTimer&&(clearTimeout(f.reconnectTimer),f.reconnectTimer=null),f.connection?.socket?.close(1e3,"manual reconnect"),f.connection=null,f.connect?.()},At=()=>{f.stopped=!1;let e=g(),t=[],r=[],o=new Map,n=new Map,s=null,i=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}),tt().catch(d=>{let m=d instanceof Error?d.message:"worker auto update failed";console.error("[worker] auto update failed",m)}),W({autoInstall:!0}).catch(d=>{let m=d instanceof Error?d.message:"worker runtime bootstrap failed";console.error("[worker] runtime bootstrap failed",m)}),te(e.workspaceRoot),Se();let l=()=>{y({queuedTaskIds:r,runningTaskIds:t,lastTaskAt:new Date().toISOString()})},u=()=>{for(e=g();t.length<Math.max(1,e.maxConcurrency)&&r.length>0;){let d=r[0];r=r.slice(1);let m=o.get(d);if(!m)continue;t=[...t,d],l();let k=vt({task:m,executorId:e.executorId,workspaceRoot:e.workspaceRoot,projectBindings:e.projectBindings,cloudUrl:e.cloudUrl,executorToken:e.executorToken,connection:s??f.connection,onStart(){},onFinish(w){o.delete(w),n.delete(w),t=t.filter(h=>h!==w),l(),u()}});n.set(d,k)}},p=d=>{a=f.stopped,!(a||i)&&(i=setTimeout(()=>{i=null,f.reconnectTimer=null,x()},5e3),f.reconnectTimer=i,y({daemonMode:"disconnected",connected:!1,paired:!0,lastDisconnectAt:new Date().toISOString(),lastError:d}))},x=()=>{if(!f.stopped){if(e=g(),!e.executorId||!e.executorToken){y({daemonMode:"unpaired",paired:!1,connected:!1,executorId:void 0,config:e,lastError:"\u5F53\u524D worker \u5C1A\u672A\u5B8C\u6210 pairing\uFF0C\u65E0\u6CD5\u8FDE\u63A5\u63A7\u5236\u9762"});return}y({daemonMode:"starting",connected:!1,paired:!0,executorId:e.executorId,config:e,lastConnectAttemptAt:new Date().toISOString()});try{let d=Ve(e,{onOpen(){f.connection?.socket===d.socket&&(e=g(),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:De(),platform:process.platform,version:_()}))},onMessage(m){f.connection?.socket===d.socket&&Tt({expectedSocket:d.socket,getConnection:()=>s,getCurrentSocket:()=>f.connection?.socket,openTerminalSession:Dt,runTerminalCommand:Bt,terminalSessions:Go,assignedTasks:o,activeExecutions:n,getConfig:()=>e,setConfig:k=>{e=k},getQueuedTaskIds:()=>r,setQueuedTaskIds:k=>{r=k},getRunningTaskIds:()=>t,setRunningTaskIds:k=>{t=k},syncRuntimeState:l,drainAssignedQueue:u})(m)},onError(m){f.connection?.socket===d.socket&&(console.error("[worker] websocket error",m),p(m))},onClose(m){if(f.connection?.socket!==d.socket)return;let k=m.reason?.trim()||`code=${m.code}`;console.log("[worker] websocket disconnected from control plane",k),p(`\u63A7\u5236\u9762\u8FDE\u63A5\u5DF2\u5173\u95ED (${k})`)}});s=d,f.connection=d}catch(d){let m=d instanceof Error?d.message:"worker websocket init failed";console.error("[worker] failed to start websocket connection",m),p(m)}}};f.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(!s||!T().connected)return;let d=new Date().toISOString();y({daemonMode:"running",connected:!0,paired:!0,lastHeartbeatAt:d,queuedTaskIds:r,runningTaskIds:t}),s.send({type:"executor.heartbeat",executorId:e.executorId,runningTaskIds:t,queuedTaskIds:r,sshPubkey:De(),at:d})},15e3)};var Ot=process.argv[2]||"daemon";switch(Ot){case"daemon":{At();break}case"open":{Mt();break}case"doctor":{Te();break}case"reset":{Q(),console.log("[worker] local config cleared");break}case"update":{z().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");rt(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: ${Ot}`),process.exitCode=1}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vibemux-worker-preview",
|
|
3
|
+
"version": "0.1.0-preview.3",
|
|
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
|
+
}
|