@workermill/agent 0.8.5 → 0.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,65 +1,32 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * WorkerMill Remote Agent CLI
4
- *
5
- * Smart default: running `workermill-agent` with no args auto-launches
6
- * setup if no config exists, or starts the agent if already configured.
7
- */
8
- import { existsSync } from "fs";
9
- import { Command } from "commander";
10
- import { setupCommand } from "./commands/setup.js";
11
- import { startCommand } from "./commands/start.js";
12
- import { stopCommand } from "./commands/stop.js";
13
- import { statusCommand } from "./commands/status.js";
14
- import { logsCommand } from "./commands/logs.js";
15
- import { pullCommand } from "./commands/pull.js";
16
- import { updateCommand } from "./commands/update.js";
17
- import { getConfigFile } from "./config.js";
18
- import { AGENT_VERSION } from "./version.js";
19
- const program = new Command();
20
- program
21
- .name("workermill-agent")
22
- .description("WorkerMill Remote Agent - Run AI workers locally with your Claude Max subscription")
23
- .version(AGENT_VERSION);
24
- program
25
- .command("setup")
26
- .description("Interactive setup wizard - configure API key, validate prerequisites, pull worker image")
27
- .action(setupCommand);
28
- program
29
- .command("start")
30
- .description("Start the agent, polling the cloud API for tasks")
31
- .option("--detach", "Run in background (daemon mode)")
32
- .action(startCommand);
33
- program
34
- .command("stop")
35
- .description("Stop the running agent")
36
- .action(stopCommand);
37
- program
38
- .command("status")
39
- .description("Show agent status, active containers, and API connectivity")
40
- .action(statusCommand);
41
- program
42
- .command("logs")
43
- .description("Live tail of agent logs (like tail -f)")
44
- .option("-n, --lines <count>", "Number of lines to show initially", "50")
45
- .action(logsCommand);
46
- program
47
- .command("pull")
48
- .description("Pull the latest worker Docker image")
49
- .action(pullCommand);
50
- program
51
- .command("update")
52
- .description("Update the agent to the latest version")
53
- .action(updateCommand);
54
- // If no command given, auto-detect: run setup if no config, otherwise start
55
- if (process.argv.length <= 2) {
56
- if (existsSync(getConfigFile())) {
57
- startCommand({ detach: false });
58
- }
59
- else {
60
- setupCommand();
61
- }
62
- }
63
- else {
64
- program.parse();
65
- }
2
+ var pt=Object.defineProperty;var mt=(e,o)=>()=>(e&&(o=e(e=0)),o);var ft=(e,o)=>{for(var n in o)pt(e,n,{get:o[n],enumerable:!0})};var $o={};ft($o,{checkPrerequisites:()=>Ze,findClaudePath:()=>ne,getConfigDir:()=>At,getConfigFile:()=>Z,getLogFile:()=>Ee,getPidFile:()=>ie,getSystemInfo:()=>Ne,loadConfig:()=>fo,loadConfigFromFile:()=>pe,saveConfigToFile:()=>Qe,validatePrerequisites:()=>ho});import{existsSync as Pe,readFileSync as ht,mkdirSync as $t,writeFileSync as wt,chmodSync as yt}from"fs";import{execSync as ge}from"child_process";import{hostname as mo,homedir as ke}from"os";import{join as Q}from"path";function At(){return be}function Z(){return Ae}function ie(){return It}function Ee(){return kt}function pe(){Pe(Ae)||(console.error("No config found. Run 'workermill-agent setup' first."),process.exit(1));let e;try{e=ht(Ae,"utf-8")}catch{console.error("Failed to read config file:",Ae),process.exit(1)}let o;try{o=JSON.parse(e)}catch{console.error("Config file is corrupted. Re-run 'workermill-agent setup'."),process.exit(1)}(!o.apiUrl||!o.apiKey)&&(console.error("Config file is missing required fields (apiUrl, apiKey). Re-run 'workermill-agent setup'."),process.exit(1));let n=o.workerImage||"workermill-worker:local";return{apiUrl:o.apiUrl,apiKey:o.apiKey,agentId:o.agentId,maxWorkers:o.maxWorkers||4,pollIntervalMs:o.pollIntervalMs||5e3,heartbeatIntervalMs:o.heartbeatIntervalMs||3e4,githubToken:o.tokens?.github||"",bitbucketToken:o.tokens?.bitbucket||"",gitlabToken:o.tokens?.gitlab||"",workerImage:n}}function Qe(e){Pe(be)||$t(be,{recursive:!0}),wt(Ae,JSON.stringify(e,null,2),"utf-8");try{yt(Ae,384)}catch{}}function fo(){let e=process.env.WORKERMILL_API_URL,o=process.env.WORKERMILL_API_KEY;return e||(console.error("WORKERMILL_API_URL is required in .env.remote"),process.exit(1)),o||(console.error("WORKERMILL_API_KEY is required in .env.remote"),console.error("Get your API key from Settings > Integrations on the WorkerMill dashboard."),process.exit(1)),{apiUrl:e.replace(/\/$/,""),apiKey:o,agentId:process.env.AGENT_ID||`agent-${mo()}`,maxWorkers:parseInt(process.env.MAX_WORKERS||"4",10),pollIntervalMs:parseInt(process.env.POLL_INTERVAL_MS||"5000",10),heartbeatIntervalMs:parseInt(process.env.HEARTBEAT_INTERVAL_MS||"30000",10),githubToken:process.env.GITHUB_TOKEN||"",bitbucketToken:process.env.BITBUCKET_TOKEN||"",gitlabToken:process.env.GITLAB_TOKEN||"",workerImage:process.env.WORKER_IMAGE||"workermill-worker:local"}}function ne(){let e=process.platform==="win32",o=e?"where":"which";try{return ge(`${o} claude`,{stdio:"ignore",timeout:1e4}),"claude"}catch{}let n=[];e?n.push(Q(process.env.ProgramFiles||"C:\\Program Files","ClaudeCode","claude.exe"),Q(process.env.LOCALAPPDATA||"","Programs","ClaudeCode","claude.exe"),Q(ke(),"AppData","Local","Programs","ClaudeCode","claude.exe"),Q(ke(),".local","bin","claude.exe")):n.push(Q(ke(),".local","bin","claude"),"/opt/homebrew/bin/claude","/usr/local/bin/claude");for(let t of n)if(t&&Pe(t))return t;return null}function Ze(e){let o=[],n=e||"workermill-worker:local";try{let l=ge("docker version --format {{.Server.Version}}",{encoding:"utf-8",timeout:1e4}).trim();o.push({name:"Docker",ok:!0,detail:l})}catch{o.push({name:"Docker",ok:!1,detail:"Not running or not installed"})}let t=ne();if(t)try{let l=ge(`"${t}" --version`,{encoding:"utf-8",timeout:1e4}).trim();o.push({name:"Claude CLI",ok:!0,detail:l})}catch{o.push({name:"Claude CLI",ok:!0,detail:t})}else o.push({name:"Claude CLI",ok:!1,detail:"Not installed"});let r=ke(),s=Q(r,".claude",".credentials.json");Pe(s)?o.push({name:"Claude auth",ok:!0,detail:"Credentials found"}):o.push({name:"Claude auth",ok:!1,detail:"Run 'claude' and complete sign-in"});let i=process.version;parseInt(i.slice(1).split(".")[0],10)>=20?o.push({name:"Node.js",ok:!0,detail:i}):o.push({name:"Node.js",ok:!1,detail:`${i} (need >= 20)`});try{ge(`docker image inspect ${n}`,{stdio:"ignore",timeout:1e4}),o.push({name:"Worker image",ok:!0,detail:n})}catch{o.push({name:"Worker image",ok:!1,detail:`'${n}' not found`})}return o}function ho(){try{ge("docker version",{stdio:"ignore"})}catch{console.error("Docker is not available. Please install Docker and ensure it's running."),process.exit(1)}let e=process.env.WORKER_IMAGE||"workermill-worker:local";try{ge(`docker image inspect ${e}`,{stdio:"ignore"})}catch{console.error(`Worker image '${e}' not found.`),console.error(e==="workermill-worker:local"?"Build it with: ./bin/local-workermill build-worker":`Pull it with: docker pull ${e}`),process.exit(1)}ne()||(console.error("Claude CLI is not installed."),console.error("Install it: curl -fsSL https://claude.ai/install.sh | bash"),process.exit(1));let o=ke(),n=Q(o,".claude",".credentials.json");Pe(n)||(console.error("Claude credentials not found."),console.error("Run 'claude' and complete the sign-in flow to authenticate."),process.exit(1))}function Ne(){let e="unknown";try{e=ge("docker version --format {{.Server.Version}}",{encoding:"utf-8",timeout:1e4}).trim()}catch{}let o="unknown",n=ne();if(n)try{o=ge(`"${n}" --version`,{encoding:"utf-8",timeout:1e4}).trim()}catch{}return{hostname:mo(),platform:process.platform,nodeVersion:process.version,dockerVersion:e,claudeVersion:o}}var be,Ae,It,kt,ee=mt(()=>{"use strict";be=Q(ke(),".workermill"),Ae=Q(be,"config.json"),It=Q(be,"agent.pid"),kt=Q(be,"agent.log")});import{existsSync as On}from"fs";import{Command as Fn}from"commander";ee();import u from"chalk";import Se from"ora";import eo from"inquirer";import{execSync as ae,spawnSync as wo}from"child_process";import{existsSync as Fe}from"fs";import{hostname as bt,homedir as Io,totalmem as Et,cpus as St}from"os";import{join as Oe}from"path";import Tt from"axios";var he=process.platform==="win32";function to(e){try{return ae(`${he?"where":"which"} ${e}`,{stdio:"ignore",timeout:1e4}),!0}catch{return!1}}function oo(e){try{return ae(`"${e}" --version`,{encoding:"utf-8",timeout:1e4}).trim()}catch{return null}}function yo(){if(!he)return null;if(to("git"))try{let o=ae("where git",{encoding:"utf-8",timeout:1e4}).trim().split(`
3
+ `)[0],n=Oe(o,"..","..","bin","bash.exe");if(Fe(n))return n}catch{}let e=[Oe(process.env.ProgramFiles||"C:\\Program Files","Git","bin","bash.exe"),"C:\\Program Files\\Git\\bin\\bash.exe","C:\\Program Files (x86)\\Git\\bin\\bash.exe",Oe(Io(),"scoop","apps","git","current","bin","bash.exe")];for(let o of e)if(Fe(o))return o;return null}function Ct(){if(he)try{return ae("winget install Anthropic.ClaudeCode --accept-package-agreements --accept-source-agreements",{stdio:"inherit",timeout:18e4}),!0}catch{return!1}else{if(process.platform==="darwin")try{return ae("brew install --cask claude-code",{stdio:"inherit",timeout:18e4}),!0}catch{}try{return ae("curl -fsSL https://claude.ai/install.sh | bash",{stdio:"inherit",timeout:12e4}),!0}catch{return!1}}}async function no(){console.log(),console.log(u.bold.cyan(" WorkerMill Remote Agent Setup")),console.log(u.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(),console.log(" Run AI workers locally with your Claude Max subscription."),console.log(" Workers execute on your machine, logs stream to the cloud dashboard."),console.log();let e=Math.round(Et()/(1024*1024*1024)),o=St().length;console.log(u.dim(" System")),console.log(` ${u.dim("RAM:")} ${e} GB ${u.dim("CPUs:")} ${o}`),e<8?(console.log(),console.log(u.red(" \u2717 Insufficient RAM")),console.log(u.yellow(" WorkerMill requires at least 8 GB of RAM (16 GB recommended).")),console.log(u.yellow(` Your system has ${e} GB.`)),console.log(),process.exit(1)):e<16?(console.log(u.yellow(` \u26A0 ${e} GB RAM is below the recommended 16 GB.`)),console.log(u.yellow(" Workers may run slowly or be killed by the OS under memory pressure."))):console.log(u.green(" \u2713 System meets requirements")),console.log();let n=Se("Checking Docker...").start();if(to("docker")){let c=oo("docker");try{let k=ae("docker info --format {{.OSType}}",{encoding:"utf-8",timeout:15e3}).trim();he&&k==="windows"&&(n.fail("Docker is running in Windows containers mode"),console.log(),console.log(u.yellow(" WorkerMill workers require Linux containers.")),console.log(u.yellow(" Right-click the Docker Desktop icon in the system tray \u2192")),console.log(u.yellow(" 'Switch to Linux containers...'")),console.log(),console.log(" Then re-run: workermill-agent"),process.exit(1))}catch{n.fail("Docker is installed but the daemon is not running"),console.log(),console.log(u.yellow(" Start Docker Desktop, wait for it to fully initialize,")),console.log(u.yellow(" then re-run: workermill-agent")),process.exit(1)}n.succeed(`Docker ${u.dim(c?`(${c})`:"")}`)}else n.fail("Docker is not installed"),console.log(),console.log(u.yellow(" Docker Desktop is required but must be installed manually:")),console.log(` ${u.cyan("https://docs.docker.com/get-docker/")}`),console.log(),console.log(" Install Docker, start it, then re-run: workermill-agent"),process.exit(1);if(he){let c=Se("Checking Git for Windows...").start(),k=yo();k?(c.succeed(`Git for Windows ${u.dim(`(${k})`)}`),process.env.CLAUDE_CODE_GIT_BASH_PATH=k):(c.fail("Git for Windows is not installed"),console.log(),console.log(u.yellow(" Claude Code on Windows requires Git for Windows (Git Bash):")),console.log(` ${u.cyan("https://git-scm.com/downloads/win")}`),console.log(),console.log(" Install Git for Windows, then re-run: workermill-agent"),process.exit(1))}let t=Se("Checking Claude CLI...").start(),r=ne();if(r){let c=oo(r);t.succeed(`Claude CLI ${u.dim(c?`(${c})`:"")}`)}else if(t.warn("Claude CLI not found \u2014 installing..."),console.log(),Ct()&&(r=ne()),r){let k=oo(r);console.log(),console.log(u.green(` \u2713 Claude CLI installed ${u.dim(k?`(${k})`:"")}`))}else console.log(),console.log(u.red(" Claude CLI was installed but could not be found.")),console.log(),he?(console.log(u.yellow(" Winget updated your PATH but this shell doesn't have it yet.")),console.log(u.yellow(" Close this terminal, open a new one, and re-run: workermill-agent"))):(console.log(" Try manually:"),console.log(u.cyan(" curl -fsSL https://claude.ai/install.sh | bash")),console.log(" Then re-run: workermill-agent")),process.exit(1);let s=Se("Checking Claude authentication...").start(),i=Oe(Io(),".claude",".credentials.json");if(Fe(i))s.succeed("Claude authenticated");else{s.warn("Not authenticated \u2014 launching Claude..."),console.log(),console.log(u.dim(" Claude will open and prompt you to authenticate.")),console.log(u.dim(" Sign in with your Claude Max account, then exit Claude (Ctrl+C).")),console.log();let c={...process.env};if(he){let k=yo();k&&(c.CLAUDE_CODE_GIT_BASH_PATH=k)}wo(r,[],{stdio:"inherit",timeout:3e5,env:c}),Fe(i)?(console.log(),console.log(u.green(" \u2713 Claude authenticated"))):(console.log(),console.log(u.red(" Authentication failed or was cancelled.")),console.log(" Try manually: open a new terminal and run 'claude'"),console.log(" Then re-run: workermill-agent"),process.exit(1))}console.log(u.green(" \u2713")+` Node.js ${u.dim(`(${process.version})`)}`),console.log(),console.log(u.bold("Configuration")),console.log();let{apiUrl:a}=await eo.prompt([{type:"input",name:"apiUrl",message:"WorkerMill API URL:",default:"https://workermill.com",validate:c=>c.startsWith("http://")||c.startsWith("https://")?!0:"Must be a valid URL"}]),{apiKey:l}=await eo.prompt([{type:"password",name:"apiKey",message:"API Key (from Settings > Integrations):",mask:"*",validate:c=>c.length>0?!0:"API key is required"}]),f=Se("Validating API key...").start(),w="github",g="",h="";try{let c=await Tt.get(`${a.replace(/\/$/,"")}/api/agent/config`,{headers:{"x-api-key":l},timeout:15e3});w=c.data.scmProvider||"github",g=c.data.workerImageUrl||"",h=c.data.ecrRegistry||"",f.succeed(`Connected! SCM provider: ${w}`)}catch(c){c.response?.status===401?f.fail("Invalid API key. Check Settings > Integrations on the dashboard."):f.fail("Failed to connect to WorkerMill API. Check the URL and try again."),process.exit(1)}console.log(u.dim(" SCM tokens are managed via Settings > Integrations on the dashboard.")),console.log();let{agentId:P}=await eo.prompt([{type:"input",name:"agentId",message:"Agent name:",default:`agent-${bt()}`}]);console.log();let d=g||"workermill-worker:local",y=h.length>0,C=Se("Checking AWS CLI...").start();if(to("aws"))try{ae("aws sts get-caller-identity",{stdio:"pipe",timeout:15e3}),C.succeed("AWS CLI configured")}catch{C.warn("AWS CLI found but credentials not configured"),console.log(),console.log(u.yellow(" Configure AWS credentials for private ECR image access:")),console.log(u.cyan(" aws configure")),console.log(u.dim(" Contact your WorkerMill admin for AWS access key / secret key.")),console.log(u.dim(" Setup will continue \u2014 you can configure AWS later before starting."))}else C.warn("AWS CLI not found"),console.log(),console.log(u.yellow(" AWS CLI is required for pulling worker images from private ECR.")),console.log(u.cyan(" Install: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html")),console.log(u.dim(" Setup will continue \u2014 install AWS CLI before starting the agent."));let x=!1;if(y){console.log(),console.log(u.dim(" Authenticating with private ECR..."));let c=h.match(/\.ecr\.([a-z0-9-]+)\.amazonaws\.com/),k=c?c[1]:"us-east-1";try{ae(`aws ecr get-login-password --region ${k} | docker login --username AWS --password-stdin ${h}`,{stdio:"pipe",timeout:3e4}),console.log(u.green(" \u2713 ECR authenticated")),x=!0}catch{console.log(u.yellow(" \u26A0 ECR authentication failed (AWS credentials may not be configured yet)")),console.log(u.dim(" Worker image pull will be skipped \u2014 authenticate later with:")),console.log(u.dim(` aws ecr get-login-password --region ${k} | docker login --username AWS --password-stdin ${h}`))}}if(x||!y){console.log(),console.log(u.dim(` Pulling worker image: ${d}`)),console.log(u.dim(" This may take a few minutes on first run (~1.1 GB)...")),console.log();let c=wo("docker",["pull",d],{stdio:"inherit",timeout:6e5});c.status===0?(console.log(),console.log(u.green(" \u2713 Worker image pulled"))):(console.log(),console.log(u.red(" \u2717 Failed to pull worker image.")),c.error&&console.log(u.yellow(` Error: ${c.error.message}`)),console.log(u.dim(" Setup will continue \u2014 you can pull the image later before starting.")))}let W={apiUrl:a.replace(/\/$/,""),apiKey:l,agentId:P,maxWorkers:1,pollIntervalMs:5e3,heartbeatIntervalMs:3e4,tokens:{github:"",bitbucket:"",gitlab:""},workerImage:d,setupCompletedAt:new Date().toISOString()};Qe(W),console.log(),console.log(u.green.bold(" Setup complete!")),console.log(),console.log(` Config saved to: ${u.dim(Z())}`),console.log(),console.log(` Start the agent with: ${u.cyan("workermill-agent start")}`),console.log()}import D from"chalk";import{totalmem as $n}from"os";import{spawn as wn}from"child_process";import{writeFileSync as st,existsSync as yn,unlinkSync as In,openSync as kn,createWriteStream as An}from"fs";import{createRequire as Rt}from"module";var _t=Rt(import.meta.url),Pt=_t("../package.json"),z=Pt.version;ee();import K from"chalk";import xt from"axios";var ro=null;function ko(e,o){ro=xt.create({baseURL:e,headers:{"Content-Type":"application/json","x-api-key":o},timeout:3e4})}var O=new Proxy({},{get(e,o){if(!ro)throw new Error("API client not initialized. Call initApi() first.");return ro[o]}});import T from"chalk";ee();import p from"chalk";import{spawn as en,execSync as lo}from"child_process";import{spawn as Dt}from"child_process";import $e from"chalk";import De from"axios";var vt=16384,Mt=6e5;async function Ao(e,o,n,t,r){let s=r?.maxTokens??vt,i=r?.temperature??.7,a=r?.timeoutMs??Mt;switch(e){case"anthropic":return Lt(o,n,t,s,i,a);case"openai":return Nt(o,n,t,s,i,a);case"google":return Ot(o,n,t,s,i,a);case"ollama":return Ft(o,n,t,s,i,a);default:throw new Error(`Unsupported AI provider: ${e}`)}}async function Lt(e,o,n,t,r,s){let a=(await De.post("https://api.anthropic.com/v1/messages",{model:e,max_tokens:t,temperature:r,messages:[{role:"user",content:o}]},{headers:{"x-api-key":n,"anthropic-version":"2023-06-01","Content-Type":"application/json"},timeout:s})).data?.content;if(Array.isArray(a))return a.filter(l=>l.type==="text").map(l=>l.text).join("");throw new Error("Unexpected Anthropic API response format")}async function Nt(e,o,n,t,r,s){let a=(await De.post("https://api.openai.com/v1/chat/completions",{model:e,max_tokens:t,temperature:r,messages:[{role:"user",content:o}]},{headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json"},timeout:s})).data?.choices?.[0]?.message;if(a?.content)return a.content;throw new Error("Unexpected OpenAI API response format")}async function Ot(e,o,n,t,r,s){let l=(await De.post(`https://generativelanguage.googleapis.com/v1beta/models/${e}:generateContent?key=${n}`,{contents:[{parts:[{text:o}]}],generationConfig:{maxOutputTokens:t,temperature:r}},{headers:{"Content-Type":"application/json"},timeout:s})).data?.candidates?.[0]?.content?.parts?.[0]?.text;if(l)return l;throw new Error("Unexpected Google AI API response format")}async function Ft(e,o,n,t,r,s){let i=n||"http://localhost:11434",l=(await De.post(`${i}/api/generate`,{model:e,prompt:o,stream:!1,options:{temperature:r}},{headers:{"Content-Type":"application/json"},timeout:s})).data?.response;if(l)return l;throw new Error("Unexpected Ollama API response format")}var Ue=null;async function Ut(){if(Ue)return Ue;try{let{data:e}=await O.get("/api/agent/critic-prompt");return Ue={promptTemplate:e.promptTemplate,approvalThreshold:e.approvalThreshold??85,maxTargetFiles:e.maxTargetFiles??15},Ue}catch{return console.warn("Failed to fetch critic config from API, using fallback defaults"),{promptTemplate:`Review this execution plan and score it 0-100.
4
+
5
+ ## PRD
6
+ {{PRD}}
7
+
8
+ ## PLAN
9
+ {{PLAN}}
10
+
11
+ Respond with JSON: {"approved": boolean, "score": number, "risks": [], "suggestions": []}`,approvalThreshold:85,maxTargetFiles:15}}}var xe=15,oe=85;function Eo(e){let o=e.indexOf("```json");if(o!==-1){let t=o+7,r=e.indexOf("{",t);if(r!==-1){let s=bo(e,r);if(s)return JSON.parse(s)}}let n=e.indexOf('"stories"');if(n!==-1){let r=e.substring(0,n).lastIndexOf("{");if(r!==-1){let s=bo(e,r);if(s)return JSON.parse(s)}}throw new Error("Could not find JSON execution plan in output")}function bo(e,o){let n=0,t=!1,r=!1;for(let s=o;s<e.length;s++){let i=e[s];if(r){r=!1;continue}if(i==="\\"){t&&(r=!0);continue}if(i==='"'){t=!t;continue}if(!t){if(i==="{")n++;else if(i==="}"&&(n--,n===0))return e.substring(o,s+1)}}return null}function So(e){let o=0,n=[];for(let t of e.stories)if(!t.targetFiles||!Array.isArray(t.targetFiles))t.targetFiles=[];else if(t.targetFiles.length>xe){let r=t.targetFiles.slice(xe);n.push(`${t.id}: ${t.targetFiles.length} files \u2192 ${xe} (dropped: ${r.join(", ")})`),t.targetFiles=t.targetFiles.slice(0,xe),o++}return{truncatedCount:o,details:n}}function To(e,o){if(e.stories.length<=o)return{droppedCount:0,details:[]};let n=e.stories.length-o,r=e.stories.slice(o).map(i=>`${i.id}: "${i.title}" (${i.persona})`);e.stories=e.stories.slice(0,o);let s=new Set(e.stories.map(i=>i.id));for(let i of e.stories)i.dependencies=i.dependencies.filter(a=>s.has(a));return{droppedCount:n,details:r}}function Co(e){let o=new Map,n=0,t=[];for(let r of e.stories){if(!r.targetFiles||r.targetFiles.length===0)continue;let s=[],i=[];for(let a of r.targetFiles)o.get(a)?i.push(a):(o.set(a,r.id),s.push(a));i.length>0&&(r.targetFiles=s,n+=i.length,t.push(`${r.id}: removed ${i.join(", ")} (owned by ${i.map(a=>o.get(a)).join(", ")})`))}return{resolvedCount:n,details:t}}function Ro(e){return"```json\n"+JSON.stringify(e,null,2)+"\n```"}async function Wt(e,o){let n=await Ut();oe=n.approvalThreshold,xe=n.maxTargetFiles;let t=JSON.stringify(o,null,2);return n.promptTemplate.replace("{{PRD}}",e).replace("{{PLAN}}",t)}function Gt(e){let o=e.trim();if(o.includes("```")){let r=o.match(/```(?:json)?\s*([\s\S]*?)```/);r&&(o=r[1].trim())}let n=o.indexOf("{");n>0&&(o=o.substring(n));let t=JSON.parse(o);return{approved:t.approved,score:Math.max(0,Math.min(100,Math.round(t.score))),risks:t.risks||[],suggestions:t.suggestions,storyFeedback:Array.isArray(t.storyFeedback)?t.storyFeedback:void 0}}function Kt(e,o,n,t,r){return new Promise((s,i)=>{let a=Dt(e,["--print","--model",o,"--permission-mode","bypassPermissions"],{env:t,stdio:["pipe","pipe","pipe"]});a.stdin.write(n),a.stdin.end();let l="",f="";a.stdout.on("data",g=>{let h=g.toString();l+=h;let P=h.split(`
12
+ `).filter(d=>d.trim());for(let d of P){let y=d.trim().length>200?d.trim().substring(0,200)+"\u2026":d.trim();y&&(r&&xo(r,`${Po} [critic] ${y}`,"output"),console.log(`${We()} ${$e.dim("\u{1F50D}")} ${$e.dim(y)}`))}}),a.stderr.on("data",g=>{f+=g.toString()});let w=setTimeout(()=>{a.kill("SIGTERM"),i(new Error("Critic CLI timed out after 20 minutes"))},12e5);a.on("exit",g=>{clearTimeout(w),g!==0?i(new Error(`Critic CLI failed (exit ${g}): ${f.substring(0,300)}`)):s(l)}),a.on("error",g=>{clearTimeout(w),i(g)})})}function _o(e){let o=["","## CRITIC FEEDBACK \u2014 Your previous plan was REJECTED","",`Score: ${e.score}/100 (need >= ${oe} to pass)`,""];if(e.risks.length>0){o.push("### Risks Identified:");for(let n of e.risks)o.push(`- ${n}`);o.push("")}if(e.suggestions&&e.suggestions.length>0){o.push("### Required Changes:");for(let n of e.suggestions)o.push(`- ${n}`);o.push("")}if(e.storyFeedback&&e.storyFeedback.length>0){o.push("### Per-Story Feedback:");for(let n of e.storyFeedback)if(o.push(`- **${n.storyId}**: ${n.feedback}`),n.suggestedChanges)for(let t of n.suggestedChanges)o.push(` - ${t}`);o.push("")}return o.push("**You MUST address ALL feedback above.** Each story must target at most 5 files.","Stories MUST NOT overlap on targetFiles. Generate a revised plan."),o.join(`
13
+ `)}var Po="[\u{1F5FA}\uFE0F planning_agent \u{1F916}]";function We(){return $e.dim(new Date().toLocaleTimeString())}async function xo(e,o,n="system",t="info"){try{await O.post("/api/control-center/logs",{taskId:e,type:n,message:o,severity:t})}catch{}}async function vo(e,o,n,t,r,s,i,a,l){let f=await Wt(n,t),w=i||"anthropic";console.log(`${We()} ${s} ${$e.dim(`Running critic validation (${w})...`)}`),l&&xo(l,`${Po} Running critic validation (${w})...`);try{let g;if(w==="anthropic")g=await Kt(e,o,f,r,l);else{if(!a)throw new Error(`No API key for critic provider "${w}"`);g=await Ao(w,o,f,a,{maxTokens:4096,temperature:.3,timeoutMs:12e5})}let h=Gt(g),P=h.score>=oe?$e.green("\u2713"):$e.red("\u2717");return console.log(`${We()} ${s} ${P} Critic score: ${h.score}/100 (threshold: ${oe})`),h}catch(g){let h=g instanceof Error?g.message:String(g);return console.error(`${We()} ${s} ${$e.yellow("\u26A0")} Critic failed: ${h.substring(0,100)}`),null}}import{generateText as Bt,tool as so,stepCountIs as jt}from"ai";import{createOpenAI as Mo}from"@ai-sdk/openai";import{createAnthropic as Vt}from"@ai-sdk/anthropic";import{createGoogleGenerativeAI as qt}from"@ai-sdk/google";import{z as me}from"zod";import{execSync as io}from"child_process";import{readFileSync as zt,existsSync as Ht}from"fs";function Yt(e,o,n){switch(e){case"anthropic":return Vt({apiKey:n})(o);case"openai":return Mo({apiKey:n})(o);case"google":return qt({apiKey:n})(o);case"ollama":return Mo({baseURL:n||"http://localhost:11434/v1",apiKey:"ollama"})(o);default:throw new Error(`Unsupported AI provider: ${e}`)}}var Jt=me.object({pattern:me.string().describe("Glob pattern like '**/*.ts', 'src/**/*.js', 'package.json'")}),Xt=me.object({path:me.string().describe("File path relative to the working directory"),limit:me.number().optional().describe("Max number of lines to read (default: 500)")}),Qt=me.object({pattern:me.string().describe("Search pattern (regex supported)"),glob:me.string().optional().describe("File glob to filter (e.g. '*.ts', '*.py')")});function Zt(e){return{glob:so({description:"Find files matching a glob pattern. Returns file paths relative to the working directory.",inputSchema:Jt,execute:async o=>{try{let n=io(`find . -path './.git' -prune -o -path './node_modules' -prune -o -name '${o.pattern.replace(/\*\*/g,"*")}' -print 2>/dev/null | head -200`,{cwd:e,encoding:"utf-8",timeout:15e3}).trim();return n||io("find . -path './.git' -prune -o -path './node_modules' -prune -o -type f -print 2>/dev/null | head -500",{cwd:e,encoding:"utf-8",timeout:15e3}).trim()||"No files found"}catch{return"Error running glob search"}}}),read_file:so({description:"Read the contents of a file. Returns the file text.",inputSchema:Xt,execute:async o=>{try{let n=`${e}/${o.path}`.replace(/\/\//g,"/");if(!Ht(n))return`File not found: ${o.path}`;let t=zt(n,"utf-8"),r=t.split(`
14
+ `),s=o.limit||500;return r.length>s?r.slice(0,s).join(`
15
+ `)+`
16
+ ... (truncated, ${r.length-s} more lines)`:t}catch(n){return`Error reading file: ${n instanceof Error?n.message:String(n)}`}}}),grep:so({description:"Search for a pattern in files. Returns matching lines with file paths and line numbers.",inputSchema:Qt,execute:async o=>{try{let n=o.glob?`--include='${o.glob}'`:"";return io(`grep -rn ${n} --exclude-dir=node_modules --exclude-dir=.git '${o.pattern.replace(/'/g,"'\\''")}' . 2>/dev/null | head -100`,{cwd:e,encoding:"utf-8",timeout:15e3}).trim()||"No matches found"}catch{return"No matches found"}}})}}async function Lo(e){let{provider:o,model:n,apiKey:t,prompt:r,systemPrompt:s,workingDir:i,maxTokens:a=16384,temperature:l=.7,timeoutMs:f=6e5,maxSteps:w=15,enableTools:g=!0}=e,h=Yt(o,n,t),P=g&&i?Zt(i):void 0,d=new AbortController,y=setTimeout(()=>d.abort(),f);try{return(await Bt({model:h,prompt:r,system:s,maxOutputTokens:a,temperature:l,tools:P,stopWhen:P?jt(w):void 0,abortSignal:d.signal})).text}finally{clearTimeout(y)}}function on(e,o){let n=[e.usage,e.message?.usage,e.result?.usage];for(let t of n)if(t&&typeof t=="object"){let r=t;typeof r.input_tokens=="number"&&(o.inputTokens=Math.max(o.inputTokens,r.input_tokens)),typeof r.output_tokens=="number"&&(o.outputTokens=Math.max(o.outputTokens,r.output_tokens)),typeof r.cache_creation_input_tokens=="number"&&(o.cacheCreationTokens=Math.max(o.cacheCreationTokens,r.cache_creation_input_tokens)),typeof r.cache_read_input_tokens=="number"&&(o.cacheReadTokens=Math.max(o.cacheReadTokens,r.cache_read_input_tokens))}}async function No(e,o,n,t){if(!(o.inputTokens===0&&o.outputTokens===0))try{await O.post(`/api/tasks/${e}/usage/partial`,{inputTokens:o.inputTokens,outputTokens:o.outputTokens,cacheCreationTokens:o.cacheCreationTokens,cacheReadTokens:o.cacheReadTokens,model:n,mode:t})}catch{}}var le=3;function b(){return p.dim(new Date().toLocaleTimeString())}var Te=[],fe=null;async function Fo(){for(;Te.length>0;){let e=Te.splice(0,50);try{await O.post("/api/control-center/logs/batch",{entries:e},{timeout:5e3})}catch{}}}async function v(e,o,n="system",t="info"){Te.length>=200&&Te.shift(),Te.push({taskId:e,message:o,type:n,severity:t}),fe||(fe=Fo().finally(()=>{fe=null}))}async function tn(){fe&&await fe,Te.length>0&&(fe=Fo().finally(()=>{fe=null}),await fe)}async function Ce(e,o,n,t,r,s){try{await O.post("/api/agent/planning-progress",{taskId:e,phase:o,elapsedSeconds:n,detail:t,charsGenerated:r,toolCallCount:s})}catch{}}var S="[\u{1F5FA}\uFE0F planning_agent \u{1F916}]";function Ge(e){let o=Math.floor(e/60),n=e%60;return o>0?`${o}m ${n}s`:`${n}s`}function Oo(e,o){switch(e){case"initializing":return`${S} Starting planning agent...`;case"reading_repo":return`${S} Reading repository structure...`;case"analyzing":return`${S} Analyzing requirements...`;case"generating_plan":return`${S} Planning in progress \u2014 analyzing requirements and decomposing into steps (${Ge(o)} elapsed)`;case"validating":return`${S} Validating plan...`;case"complete":return`${S} Planning complete`}}function nn(e,o,n,t,r,s,i){let a=p.cyan(r.slice(0,8));return new Promise((l,f)=>{let g=en(e,["--print","--verbose","--output-format","stream-json","--model",o,"--permission-mode","bypassPermissions"],{cwd:i,env:t,stdio:["pipe","pipe","pipe"]});g.stdin.write(n),g.stdin.end();let h="",P="",d="",y=0,C=0,x={inputTokens:0,outputTokens:0,cacheCreationTokens:0,cacheReadTokens:0},W=o,c="";function k(L=!1){if(!c)return;let q=c.split(`
17
+ `),ue=L?"":q.pop()||"";for(let H of q)if(H.trim()){v(r,`${S} ${H}`,"output");let N=H.trim().length>160?H.trim().substring(0,160)+"\u2026":H.trim();console.log(`${b()} ${a} ${p.dim("\u{1F4AD}")} ${p.dim(N)}`)}c=ue}let E="initializing",G=!1,I={started:!0,reading:!1,analyzing:!1,generating:!1};function R(L){if(L===E)return;E=L;let q=Math.round((Date.now()-s)/1e3),ue=Oo(L,q);v(r,ue),console.log(`${b()} ${a} ${p.dim(ue)}`)}let _=setInterval(()=>k(),500),se=setInterval(()=>{let L=Math.round((Date.now()-s)/1e3);Ce(r,E,L,Oo(E,L),y,C)},2e3),B=0,J=setInterval(()=>{let L=Math.round((Date.now()-s)/1e3);if(E==="initializing"&&L>=5?R("reading_repo"):E==="reading_repo"&&L>=15&&!G&&R("analyzing"),E==="generating_plan"&&L-B>=30){B=L;let q=`${S} Planning in progress \u2014 analyzing requirements and decomposing into steps (${Ge(L)} elapsed)`;v(r,q),console.log(`${b()} ${a} ${p.dim(q)}`)}},5e3),te="";g.stdout.on("data",L=>{te+=L.toString();let q=te.split(`
18
+ `);te=q.pop()||"";for(let ue of q){let H=ue.trim();if(H)try{let N=JSON.parse(H);if(N.type==="assistant"&&N.message?.content){let $=N.message.content;if(Array.isArray($))for(let A of $)A.type==="text"&&A.text?(h+=A.text,y+=A.text.length,c+=A.text,G||(G=!0,C>0&&!I.analyzing&&(R("analyzing"),I.analyzing=!0)),y>500&&!I.generating&&(R("generating_plan"),I.generating=!0,B=Math.round((Date.now()-s)/1e3))):A.type==="tool_use"&&(C++,I.reading||(R("reading_repo"),I.reading=!0));else typeof $=="string"&&$&&(h+=$,y+=$.length,c+=$)}else N.type==="content_block_delta"&&N.delta?.text?(h+=N.delta.text,y+=N.delta.text.length,c+=N.delta.text,G||(G=!0,C>0&&!I.analyzing&&(R("analyzing"),I.analyzing=!0)),y>500&&!I.generating&&(R("generating_plan"),I.generating=!0,B=Math.round((Date.now()-s)/1e3))):N.type==="content_block_start"&&N.content_block?.type==="tool_use"?(C++,I.reading||(R("reading_repo"),I.reading=!0)):N.type==="result"&&N.result&&(P=typeof N.result=="string"?N.result:"");if(on(N,x),N.type==="result"&&N.total_cost_usd!==void 0&&N.modelUsage&&typeof N.modelUsage=="object"){let $=Object.keys(N.modelUsage);$.length>0&&(W=$[0])}}catch{h+=H+`
19
+ `,y+=H.length}}}),g.stderr.on("data",L=>{d+=L.toString()});let Le=setInterval(()=>{(x.inputTokens>0||x.outputTokens>0)&&No(r,x,W,"greatest").catch(()=>{})},3e4);function V(){clearInterval(J),clearInterval(se),clearInterval(_),clearInterval(Le),k(!0)}let de=setTimeout(()=>{V(),g.kill("SIGTERM"),f(new Error("Claude CLI timed out after 20 minutes"))},12e5);g.on("exit",L=>{clearTimeout(de),V();let q=Math.round((Date.now()-s)/1e3);Ce(r,"validating",q,"Validating plan...",y,C),No(r,x,W,"greatest").catch(()=>{}),L!==0?f(new Error(`Claude CLI failed (exit ${L}): ${d.substring(0,300)}`)):l(P||h)}),g.on("error",L=>{clearTimeout(de),V(),f(L)})})}function rn(e,o){if(o)switch(e){case"anthropic":return o.anthropicApiKey;case"openai":return o.openaiApiKey;case"google":return o.googleApiKey;case"ollama":return o.ollamaBaseUrl||"http://localhost:11434";default:return}}function sn(e,o,n){switch(n){case"bitbucket":return`https://x-token-auth:${o}@bitbucket.org/${e}.git`;case"gitlab":return`https://oauth2:${o}@gitlab.com/${e}.git`;default:return`https://x-access-token:${o}@github.com/${e}.git`}}async function an(e,o,n,t){let r=p.cyan(t.slice(0,8)),s=`/tmp/workermill-planning-${t.slice(0,8)}-${Date.now()}`;try{let i=sn(e,o,n);return console.log(`${b()} ${r} ${p.dim("Cloning repo for planner...")}`),lo(`git clone --depth 1 --single-branch "${i}" "${s}"`,{stdio:"ignore",timeout:6e4}),console.log(`${b()} ${r} ${p.green("\u2713")} Repo cloned to ${p.dim(s)}`),s}catch(i){let a=i instanceof Error?i.message:String(i);console.error(`${b()} ${r} ${p.yellow("\u26A0")} Clone failed, planner will run without repo access: ${a.substring(0,100)}`);try{lo(`rm -rf "${s}"`,{stdio:"ignore"})}catch{}return null}}async function Do(e,o,n){let t=p.cyan(e.id.slice(0,8));console.log(`${b()} ${t} Fetching planning prompt...`),await v(e.id,`${S} Fetching planning prompt from cloud API...`);let r=await O.get("/api/agent/planning-prompt",{params:{taskId:e.id}}),{prompt:s,model:i,provider:a,maxStories:l}=r.data,f=typeof l=="number"?l:8,w=i,g=a||"anthropic",h=g==="anthropic",P=process.env.CLAUDE_CLI_PATH||ne()||"claude",d={...process.env};delete d.CLAUDE_CODE_OAUTH_TOKEN;let y=rn(g,n),C=Date.now(),x=e.description||e.summary,W=null;if(e.githubRepo){let R=e.scmProvider||"github",_=R==="bitbucket"?o.bitbucketToken:R==="gitlab"?o.gitlabToken:o.githubToken;_?W=await an(e.githubRepo,_,R,e.id):console.log(`${b()} ${t} ${p.yellow("\u26A0")} No SCM token for ${R}, planner will run without repo access`)}let c=s,k=null,E=0,G=[],I=0;try{for(let _=1;_<=le;_++){let se=le>1?` (attempt ${_}/${le})`:"",B=`${g}/${w}`;_>1?(console.log(`${b()} ${t} Running planner${se} ${p.dim(`(${p.yellow(B)})`)}`),await v(e.id,`${S} Re-planning${se} using ${B}`)):(console.log(`${b()} ${t} Running planner ${p.dim(`(${p.yellow(B)})`)}`),await v(e.id,`${S} Starting planning agent using ${B}`));let J;try{if(h)J=await nn(P,w,c,d,e.id,C,W||void 0);else{if(!y)throw new Error(`No API key available for provider "${g}". Configure it in Settings > Integrations.`);let A=Math.round((Date.now()-C)/1e3);await Ce(e.id,"generating_plan",A,"Generating plan via AI SDK...",0,0),J=await Lo({provider:g,model:w,apiKey:y,prompt:c,workingDir:W||void 0,enableTools:!!W,maxSteps:10});let M=Math.round((Date.now()-C)/1e3);await Ce(e.id,"validating",M,"Validating plan...",J.length,0)}}catch(A){let M=Math.round((Date.now()-C)/1e3),X=A instanceof Error?A.message:String(A);return console.error(`${b()} ${t} ${p.red("\u2717")} Failed after ${M}s: ${X.substring(0,100)}`),await v(e.id,`${S} Planning failed after ${Ge(M)}: ${X.substring(0,200)}`,"error","error"),!1}let te=Math.round((Date.now()-C)/1e3),Le=h?"Claude CLI":`${g} API`;console.log(`${b()} ${t} ${p.green("\u2713")} ${Le} done ${p.dim(`(${te}s, ${J.length} chars)`)}`);let V;try{V=Eo(J)}catch(A){let M=A instanceof Error?A.message:String(A);return console.error(`${b()} ${t} ${p.red("\u2717")} Plan parse failed: ${M.substring(0,100)}`),await v(e.id,`${S} Failed to parse execution plan from Claude output: ${M.substring(0,200)}`,"error","error"),await ln(e.id,J,o.agentId,t,te)}let{truncatedCount:de,details:L}=So(V);if(de>0){I+=de;let A=`${S} File cap applied: ${de} stories truncated to max 5 targetFiles`;console.log(`${b()} ${t} ${p.yellow("\u26A0")} ${A}`),await v(e.id,A);for(let M of L)console.log(`${b()} ${t} ${p.dim(M)}`)}let{droppedCount:q,details:ue}=To(V,f);if(q>0){let A=`${S} Story cap applied: ${q} stories dropped (max ${f})`;console.log(`${b()} ${t} ${p.yellow("\u26A0")} ${A}`),await v(e.id,A);for(let M of ue)console.log(`${b()} ${t} ${p.dim(M)}`)}let{resolvedCount:H,details:N}=Co(V);if(H>0){let A=`${S} File overlap resolved: ${H} shared file(s) de-duped across stories`;console.log(`${b()} ${t} ${p.yellow("\u26A0")} ${A}`),await v(e.id,A);for(let M of N)console.log(`${b()} ${t} ${p.dim(M)}`)}console.log(`${b()} ${t} Plan: ${p.bold(V.stories.length)} stories (max ${f})`),await v(e.id,`${S} Plan generated: ${V.stories.length} stories (${Ge(te)}). Running critic validation...`);let $=await vo(P,w,x,V,d,t,g,y,e.id);if($&&$.score>E?(k=V,E=$.score):!$&&!k&&(k=V),$&&G.push({iteration:_,score:$.score,approved:$.approved||$.score>=oe,risks:$.risks,suggestions:$.suggestions,filesCapApplied:de>0?de:void 0}),!$){let A=`${S} \u26A0\uFE0F CRITIC BYPASSED \u2014 Critic validation failed (timeout/parse error). Posting plan WITHOUT quality gate.`;console.log(`${b()} ${t} ${p.yellow("\u26A0")} ${A}`),await v(e.id,A,"error","warning");let M=Date.now()-C;return await ao(e.id,V,o.agentId,t,te,void 0,void 0,G,I,M,_)}if($.approved||$.score>=oe){let A=`${S} Critic approved (score: ${$.score}/100)`;if(console.log(`${b()} ${t} ${p.green("\u2713")} ${A}`),await v(e.id,A),$.risks.length>0){let X=`${S} Critic risks (non-blocking): ${$.risks.join("; ")}`;console.log(`${b()} ${t} ${p.dim(X)}`),await v(e.id,X)}let M=Date.now()-C;return await ao(e.id,V,o.agentId,t,te,$.score,$.risks,G,I,M,_)}if(_<le){let A=_o($);c=s+`
20
+
21
+ `+A;let M=`${S} Critic rejected (score: ${$.score}/100, threshold: ${oe}). Re-planning with feedback...`;if(console.log(`${b()} ${t} ${p.yellow("\u26A0")} ${M}`),await v(e.id,M),$.risks.length>0){let X=`${S} Critic risks: ${$.risks.join("; ")}`;console.log(`${b()} ${t} ${p.dim(X)}`),await v(e.id,X)}if($.suggestions&&$.suggestions.length>0){let X=`${S} Critic suggestions: ${$.suggestions.join("; ")}`;console.log(`${b()} ${t} ${p.dim(X)}`),await v(e.id,X)}}else{let A=`${S} Critic rejected after ${le} iterations (best score: ${E}/100, threshold: ${oe})`;if(console.error(`${b()} ${t} ${p.red("\u2717")} ${A}`),await v(e.id,A,"error","error"),$.risks.length>0){let M=`${S} Final risks: ${$.risks.join("; ")}`;console.error(`${b()} ${t} ${M}`),await v(e.id,M,"error","error")}if($.suggestions&&$.suggestions.length>0){let M=`${S} Suggestions: ${$.suggestions.join("; ")}`;console.error(`${b()} ${t} ${M}`),await v(e.id,M,"error","error")}}}let R=50;if(k&&E>=R){let _=Math.round((Date.now()-C)/1e3),se=`${S} Best-plan fallback: posting plan with score ${E}/100 (below ${oe} threshold, above ${R} minimum)`;console.log(`${b()} ${t} ${p.yellow("\u26A0")} ${se}`),await v(e.id,se);let B=Date.now()-C;if(await ao(e.id,k,o.agentId,t,_,E,[`Best-plan fallback: critic rejected after ${le} iterations`],G,I,B,le))return!0;console.log(`${b()} ${t} ${p.yellow("\u26A0")} ${S} Fallback post rejected by server, reporting plan-failed`),await v(e.id,`${S} Fallback plan rejected by server \u2014 reporting failure`)}try{let _=k&&E>=R?`Best-plan fallback rejected by server after ${le} iterations (best score: ${E}/100)`:`Critic rejected after ${le} iterations (best score: ${E}/100, threshold: ${oe}, fallback minimum: ${R})`;await O.post("/api/agent/plan-failed",{taskId:e.id,agentId:o.agentId,reason:_,criticHistory:G})}catch{}return!1}finally{if(await tn(),W)try{lo(`rm -rf "${W}"`,{stdio:"ignore"})}catch{}}}async function ao(e,o,n,t,r,s,i,a,l,f,w){let g=Ro(o);try{let P=(await O.post("/api/agent/plan-result",{taskId:e,rawOutput:g,agentId:n,criticScore:s,criticRisks:i,criticHistory:a,criticIterations:w,fileCapTruncations:l,planningDurationMs:f})).data.storyCount;return console.log(`${b()} ${t} ${p.green("\u2713")} Plan validated: ${p.bold(P)} stories \u2192 ${p.green("queued")}`),await v(e,`${S} Plan validated: ${P} stories. Task queued for execution.`),await Ce(e,"complete",r,"Planning complete",0,0),!0}catch(h){let P=h,d=P.response?.data?.error||P.response?.data?.detail||String(h),y=P.response?.status?` (${P.response.status})`:"";return console.error(`${b()} ${t} ${p.red("\u2717")} Server validation failed${y}: ${d.substring(0,100)}`),await v(e,`${S} Server-side plan validation failed${y}: ${d.substring(0,200)}`,"error","error"),!1}}async function ln(e,o,n,t,r){try{let i=(await O.post("/api/agent/plan-result",{taskId:e,rawOutput:o,agentId:n})).data.storyCount;return console.log(`${b()} ${t} ${p.green("\u2713")} Plan validated (server-side): ${p.bold(i)} stories \u2192 ${p.green("queued")}`),await v(e,`${S} Plan validated: ${i} stories. Task queued for execution.`),await Ce(e,"complete",r,"Planning complete",0,0),!0}catch(s){let a=s.response?.data?.detail||String(s);return console.error(`${b()} ${t} ${p.red("\u2717")} Validation failed: ${a.substring(0,100)}`),await v(e,`${S} Plan validation failed: ${a.substring(0,200)}`,"error","error"),!1}}import m from"chalk";import{spawn as Wo,execSync as Be}from"child_process";import*as Ke from"path";import*as re from"fs";import*as je from"os";function F(){return m.dim(new Date().toLocaleTimeString())}var Y=new Map;function cn(){if(process.env.WSL_DISTRO_NAME||process.env.WSL_INTEROP)return!0;try{return re.readFileSync("/proc/version","utf-8").toLowerCase().includes("microsoft")}catch{return!1}}var Ve=cn(),Go=Ve||process.platform==="darwin"||process.platform==="win32";function Ko(){if(!Ve)return null;try{let e=Be("hostname -I",{encoding:"utf-8"}).trim().split(/\s+/)[0];if(e&&/^\d+\.\d+\.\d+\.\d+$/.test(e))return e}catch{}return null}function Bo(e){if(!Ve)return e;let o=e.match(/^\/mnt\/([a-zA-Z])\/(.*)$/);return o?`${o[1].toUpperCase()}:/${o[2]}`:e}function jo(){let e=Ke.join(je.homedir(),".claude");if(re.existsSync(e))return e;if(Ve){let o="/mnt/c/Users";if(re.existsSync(o))try{for(let n of re.readdirSync(o)){if(["Public","Default","Default User","All Users"].includes(n))continue;let t=Ke.join(o,n,".claude");if(re.existsSync(t))return t}}catch{}}return null}var Uo=0;function Vo(e){let o=e.match(/^(\d+\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com)/);return o?o[1]:null}function dn(e){let o=e.match(/\.ecr\.([a-z0-9-]+)\.amazonaws\.com/);return o?o[1]:"us-east-1"}function qo(e){if(Date.now()<Uo)return!0;let o=dn(e);try{return Be(`aws ecr get-login-password --region ${o} | docker login --username AWS --password-stdin ${e}`,{stdio:"pipe",timeout:3e4}),Uo=Date.now()+660*60*1e3,!0}catch{return!1}}function zo(e,o,n){if(n?.scmToken)return n.scmToken;switch(e){case"bitbucket":return o.bitbucketToken;case"gitlab":return o.gitlabToken;default:return o.githubToken}}function un(e){let o=e.jiraFields;if(!o)return!1;let n=o.labels;if(Array.isArray(n)&&n.some(s=>typeof s=="string"&&s.toLowerCase()==="self-review"))return!0;let r=o.issue?.labels;return!!(Array.isArray(r)&&r.some(s=>typeof s=="string"?s.toLowerCase()==="self-review":s&&typeof s=="object"&&"name"in s?(s.name||"").toLowerCase()==="self-review":!1))}async function Ho(e,o,n,t){let r=m.cyan(e.id.slice(0,8));if(Y.has(e.id)){console.log(`${F()} ${r} ${m.dim("Already running, skipping")}`);return}let s=`workermill-${e.id.slice(0,8)}`,a=["run","--rm",...!o.workerImage.includes("/")?[]:["--pull","always"],"--name",s],l=Math.round(je.totalmem()/(1024*1024*1024));if(l<=16?a.push("--memory","6g","--memory-swap","10g","--cpus","2"):(l<=32,a.push("--memory","6g","--memory-swap","12g","--cpus","4")),Go){let I=Ko();a.push(`--add-host=host.docker.internal:${I||"host-gateway"}`)}else a.push("--network","host");let f=e.workerProvider||"anthropic",w=jo();if(!w&&f==="anthropic"){console.error(`${F()} ${r} ${m.red("\u2717")} Claude credentials not found. Run 'claude' and complete the sign-in flow.`);return}if(w){let I=Ke.join(w,".credentials.json");try{re.chmodSync(I,438)}catch{}let R=Bo(w);a.push("-v",`${R}:/home/worker/.claude`)}else console.log(`${F()} ${r} ${m.dim("Skipping Claude mount (non-Anthropic worker)")}`);let g=o.apiUrl.replace(/localhost|127\.0\.0\.1/,"host.docker.internal"),h=e.scmProvider||"github",P=zo(h,o,t),d=t?.githubToken||o.githubToken,y=h==="bitbucket"&&t?.scmToken||o.bitbucketToken,C=h==="gitlab"&&t?.scmToken||o.gitlabToken,x={NODE_OPTIONS:"--max-old-space-size=3072",EPIC_MODE:"true",EXECUTION_MODE:"local",TASK_ID:e.id,ORG_ID:e.orgId||"",JIRA_ISSUE_KEY:e.jiraIssueKey||"",JIRA_SUMMARY:e.summary||"",JIRA_DESCRIPTION:e.description||"",TASK_SUMMARY:e.summary||"",TASK_DESCRIPTION:e.description||"",WORKER_PERSONA:e.workerPersona||"",RETRY_NUMBER:String(e.retryCount??0),TICKET_KEY:e.jiraIssueKey||"",API_BASE_URL:g,ORG_API_KEY:o.apiKey,SCM_PROVIDER:h,SCM_TOKEN:P,SCM_BASE_URL:t?.scmBaseUrl||"",GITHUB_TOKEN:d,GH_TOKEN:d,GITHUB_REVIEWER_TOKEN:t?.githubReviewerToken||"",BITBUCKET_TOKEN:y,BITBUCKET_USERNAME:t?.bitbucketUsername||"x-token-auth",GITLAB_TOKEN:C,TARGET_REPO:e.githubRepo||"",GITHUB_REPO:e.githubRepo||"",WORKER_MODEL:e.workerModel||String(n.defaultWorkerModel||""),CLAUDE_MODEL:e.workerModel||String(n.defaultWorkerModel||""),JIRA_BASE_URL:t?.jiraBaseUrl||"",JIRA_EMAIL:t?.jiraEmail||"",JIRA_API_TOKEN:t?.jiraApiToken||"",TICKET_SYSTEM:t?.issueTrackerProvider||"jira",LINEAR_API_KEY:t?.linearApiKey||"",AWS_ACCESS_KEY_ID:t?.customerAwsAccessKeyId||"",AWS_SECRET_ACCESS_KEY:t?.customerAwsSecretAccessKey||"",AWS_DEFAULT_REGION:t?.customerAwsRegion||"",AWS_REGION:t?.customerAwsRegion||"",CUSTOMER_AWS_ROLE_ARN:t?.customerAwsRoleArn||"",CUSTOMER_AWS_EXTERNAL_ID:t?.customerAwsExternalId||"",CUSTOMER_AWS_REGION:t?.customerAwsRegion||"",MANAGER_PROVIDER:t?.managerProvider||"anthropic",MANAGER_MODEL:t?.managerModelId||"",BITBUCKET_EMAIL:t?.bitbucketEmail||"",DEPLOYMENT_ENABLED:e.deploymentEnabled||e.parentTaskId?"true":"false",PRD_CHILD_TASK:e.parentTaskId?"true":"false",IMPROVEMENT_ENABLED:e.improvementEnabled?"true":"false",QUALITY_GATE_BYPASS:e.qualityGateBypass?"true":"false",STANDARD_SDK_MODE:e.standardSdkMode?"true":"false",MAX_REVIEW_REVISIONS:String(n.maxReviewRevisions??3),CODEBASE_INDEXING_ENABLED:n.codebaseIndexingEnabled===!0?"true":"false",EXISTING_PR_URL:e.githubPrUrl||"",EXISTING_PR_NUMBER:e.githubPrNumber?String(e.githubPrNumber):"",PARENT_TASK_ID:e.parentTaskId||e.id,PARENT_JIRA_KEY:e.jiraIssueKey&&/-S\d+$/.test(e.jiraIssueKey)&&e.jiraFields?.parentJiraKey||"",TARGET_BRANCH:e.jiraFields?.targetBranch||"",STORY_BRANCH:e.jiraFields?.storyBranch||"",TASK_NOTES:e.taskNotes||"",TARGET_FILES:JSON.stringify(e.jiraFields?.targetFiles||[]),REFERENCE_FILES:JSON.stringify(e.jiraFields?.referenceFiles||[]),PIPELINE_VERSION:e.jiraFields?.pipelineVersion||e.pipelineVersion||"",V2_STEP_INPUT:e.jiraFields?.v2StepInput?JSON.stringify(e.jiraFields.v2StepInput):"",EXECUTION_MODE_SETTING:e.jiraFields?.executionMode||"autonomous",ANTHROPIC_API_KEY:w?"":t?.anthropicApiKey||process.env.ANTHROPIC_API_KEY||"",WORKER_PROVIDER:e.workerProvider||"anthropic",OPENAI_API_KEY:t?.openaiApiKey||"",GOOGLE_API_KEY:t?.googleApiKey||"",GOOGLE_GENERATIVE_AI_API_KEY:t?.googleApiKey||"",OLLAMA_HOST:t?.ollamaBaseUrl||"",OLLAMA_CONTEXT_WINDOW:t?.ollamaContextWindow?String(t.ollamaContextWindow):"",VLLM_BASE_URL:t?.vllmBaseUrl||"",BLOCKER_MAX_AUTO_RETRIES:String(n.blockerMaxAutoRetries??3),BLOCKER_AUTO_RETRY_ENABLED:n.blockerAutoRetryEnabled!==!1?"true":"false",PUSH_AFTER_COMMIT:n.pushAfterCommit!==!1?"true":"false",GRACEFUL_SHUTDOWN_ENABLED:n.gracefulShutdownEnabled!==!1?"true":"false",MAX_PARALLEL_EXPERTS:String(n.maxParallelExperts??4),REVIEW_ENABLED:e.skipManagerReview===!1?"true":"false",SELF_REVIEW_ENABLED:un(e)||n.selfReviewEnabled!==!1?"true":"false"};for(let[I,R]of Object.entries(x))R!==""&&a.push("-e",`${I}=${R}`);let W=o.workerImage,c=Vo(W);c&&qo(c),a.push(W);let k=e.skipManagerReview===!1;console.log(`${F()} ${r} ${m.dim("Starting container")} ${m.yellow(s)}`),console.log(`${F()} ${r} ${m.dim(` skipManagerReview=${e.skipManagerReview} \u2192 REVIEW_ENABLED=${k}`)}`),console.log(`${F()} ${r} ${m.dim(` model=${e.workerModel} repo=${e.githubRepo}`)}`),console.log(`${F()} ${r} ${m.dim(` totalRamGB=${l} docker args:`)} ${a.slice(0,10).join(" ")}`);let E=Wo("docker",a,{stdio:["ignore","pipe","pipe"],detached:!1});if(!E.pid){console.error(`${F()} ${r} ${m.red("\u2717")} Failed to spawn container`);return}let G={taskId:e.id,containerName:s,process:E,startedAt:new Date,status:"running",resultEmitted:!1};Y.set(e.id,G),E.stdout?.on("data",I=>{let R=I.toString().split(`
22
+ `).filter(_=>_.trim());for(let _ of R)console.log(`${F()} ${r} ${m.dim(_)}`),_.includes("::result::")&&(G.resultEmitted=!0)}),E.stderr?.on("data",I=>{let R=I.toString().split(`
23
+ `).filter(_=>_.trim());for(let _ of R)console.log(`${F()} ${r} ${m.red(_)}`)}),E.on("exit",I=>{G.status=I===0?"completed":"failed";let R=Math.round((Date.now()-G.startedAt.getTime())/1e3),_=I===0?m.green("\u2713"):m.red("\u2717"),se=I===0?m.green("completed"):m.red(`failed (exit ${I})`);console.log(`${F()} ${r} ${_} Container ${se} ${m.dim(`(${R}s)`)}`),G.resultEmitted||(console.log(`${F()} ${r} ${m.yellow("\u26A0")} No ::result:: marker seen \u2014 posting fallback completion in 15s`),setTimeout(async()=>{try{let B=I===0?"completed":"failed",J=I!==0?`Worker container exited with code ${I} without reporting completion`:void 0;(await(await fetch(`${o.apiUrl}/api/tasks/${e.id}/worker-complete`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":o.apiKey},body:JSON.stringify({exitCode:I??1,result:B,errorMessage:J})})).json()).status==="ignored"?console.log(`${F()} ${r} ${m.dim("Fallback completion ignored (task already transitioned)")}`):console.log(`${F()} ${r} ${m.yellow("\u26A0")} Fallback completion applied: ${B}`)}catch(B){console.error(`${F()} ${r} ${m.red("\u2717")} Fallback completion failed:`,B instanceof Error?B.message:B)}},15e3)),setTimeout(()=>Y.delete(e.id),9e4)}),E.on("error",I=>{G.status="failed",console.error(`${F()} ${r} ${m.red("\u2717")} Container error: ${I.message}`)})}function co(){return Array.from(Y.values()).filter(e=>e.status==="running").length}function Yo(){return Array.from(Y.values()).filter(e=>e.status==="running").map(e=>e.taskId)}function Jo(e){let o=Y.get(e);if(!o||o.status!=="running")return;let n=m.cyan(e.slice(0,8));console.log(`${F()} ${n} ${m.red("\u25A0")} Stopping container (cancelled by dashboard)`);try{Be(`docker stop ${o.containerName}`,{stdio:"ignore",timeout:15e3}),o.status="completed"}catch{}Y.delete(e)}async function Xo(){console.log(`${F()} ${m.dim(`Stopping ${Y.size} containers...`)}`);for(let[,e]of Y)if(e.status==="running")try{Be(`docker stop ${e.containerName}`,{stdio:"ignore",timeout:15e3}),e.status="completed"}catch{}Y.clear()}async function Qo(e,o,n){let t=m.cyan(e.id.slice(0,8)),r=`manager-${e.id}`;if(Y.has(r))return;let s=`wm-manager-${e.id.slice(0,8)}-${Date.now()}`,i=jo(),a=["run","--rm","--name",s,"--memory=4g","--cpus=2"];if(Go){let c=Ko();a.push(`--add-host=host.docker.internal:${c||"host-gateway"}`)}else a.push("--network","host");if(i){let c=Bo(i);a.push("-v",`${c}:/home/worker/.claude`)}let l=o.apiUrl.replace(/localhost|127\.0\.0\.1/,"host.docker.internal"),f=e.scmProvider||"github",w=zo(f,o,n),g=n?.githubToken||o.githubToken,h=f==="bitbucket"&&n?.scmToken||o.bitbucketToken,P=f==="gitlab"&&n?.scmToken||o.gitlabToken,d={NODE_OPTIONS:"--max-old-space-size=3072",TASK_ID:e.id,MANAGER_ACTION:e.managerAction,JIRA_ISSUE_KEY:e.jiraIssueKey||"",JIRA_SUMMARY:e.summary||"",JIRA_DESCRIPTION:e.description||"",GITHUB_REPO:e.githubRepo||"",PR_URL:e.githubPrUrl||"",PR_NUMBER:e.githubPrNumber?String(e.githubPrNumber):"",API_BASE_URL:l,ORG_API_KEY:o.apiKey,SCM_PROVIDER:f,SCM_TOKEN:w,GITHUB_TOKEN:g,GH_TOKEN:g,BITBUCKET_TOKEN:h,BITBUCKET_USERNAME:n?.bitbucketUsername||"x-token-auth",GITLAB_TOKEN:P,MANAGER_PROVIDER:n?.managerProvider||"anthropic",MANAGER_MODEL:n?.managerModelId||"",ANTHROPIC_API_KEY:i?"":n?.anthropicApiKey||process.env.ANTHROPIC_API_KEY||"",OPENAI_API_KEY:n?.openaiApiKey||"",GOOGLE_API_KEY:n?.googleApiKey||"",JIRA_BASE_URL:n?.jiraBaseUrl||"",JIRA_EMAIL:n?.jiraEmail||"",JIRA_API_TOKEN:n?.jiraApiToken||"",TICKET_SYSTEM:n?.issueTrackerProvider||"jira",LINEAR_API_KEY:n?.linearApiKey||""};for(let[c,k]of Object.entries(d))k!==""&&a.push("-e",`${c}=${k}`);let y=o.workerImage,C=Vo(y);C&&qo(C),a.push("--entrypoint","/bin/bash"),a.push(y),a.push("/app/manager-entrypoint.sh"),console.log(`${F()} ${t} ${m.magenta("\u25C6 MANAGER")} Starting ${e.managerAction} container ${m.yellow(s)}`);let x=Wo("docker",a,{stdio:["ignore","pipe","pipe"],detached:!1});if(!x.pid){console.error(`${F()} ${t} ${m.red("\u2717")} Failed to spawn manager container`);return}let W={taskId:r,containerName:s,process:x,startedAt:new Date,status:"running",resultEmitted:!1};Y.set(r,W),x.stdout?.on("data",c=>{let k=c.toString().split(`
24
+ `).filter(E=>E.trim());for(let E of k)console.log(`${F()} ${t} ${m.magenta("MGR")} ${m.dim(E)}`)}),x.stderr?.on("data",c=>{let k=c.toString().split(`
25
+ `).filter(E=>E.trim());for(let E of k)console.log(`${F()} ${t} ${m.magenta("MGR")} ${m.red(E)}`)}),x.on("exit",c=>{W.status=c===0?"completed":"failed";let k=Math.round((Date.now()-W.startedAt.getTime())/1e3),E=c===0?m.green("\u2713"):m.red("\u2717"),G=c===0?m.green("completed"):m.red(`failed (exit ${c})`);console.log(`${F()} ${t} ${m.magenta("MGR")} ${E} Manager ${e.managerAction} ${G} ${m.dim(`(${k}s)`)}`),setTimeout(()=>Y.delete(r),6e4)}),x.on("error",c=>{W.status="failed",console.error(`${F()} ${t} ${m.magenta("MGR")} ${m.red("\u2717")} Container error: ${c.message}`)})}import{spawn as Zo}from"child_process";import ve from"chalk";async function Re(){return console.log(ve.cyan(" Updating @workermill/agent...")),new Promise(e=>{let o=Zo("npm",["install","-g","@workermill/agent@latest"],{stdio:"inherit",shell:!0});o.on("error",n=>{console.error(ve.red(` Update failed: ${n.message}`)),e(!1)}),o.on("close",n=>{n===0?(console.log(ve.green(" Update successful.")),e(!0)):(console.error(ve.red(` Update failed with exit code ${n}`)),e(!1))})})}function qe(){console.log(ve.cyan(" Restarting agent...")),Zo(process.argv[0],process.argv.slice(1),{stdio:"inherit",detached:!0}).unref(),process.exit(0)}var we=new Set,ye=new Set,ze=null,et=!1,He=!1;function U(){return T.dim(new Date().toLocaleTimeString())}async function gn(){if(ze)return ze;try{return ze=(await O.get("/api/agent/config")).data,ze}catch{return console.error(`${U()} ${T.red("\u2717")} Failed to fetch org config`),{}}}async function ot(e){if(!He)try{let o=await O.get("/api/agent/poll",{params:{agentId:e.agentId}}),n=o.data.tasks;if(n.length===0)return;for(let r of n)r.status==="planning"&&!we.has(r.id)?await pn(r,e):r.status==="queued"&&await mn(r,e);let t=o.data.managerTasks;if(t&&t.length>0)for(let r of t)ye.has(r.id)||await fn(r,e)}catch(o){let n=o,t=we.size>0||co()>0||ye.size>0;n.response?.status===401?t||console.error(`${U()} ${T.red("\u2717")} Authentication failed. Check your API key.`):t||console.warn(`${U()} ${T.yellow("\u26A0")} Poll error: ${n.message||String(o)}`)}}async function pn(e,o){let n,t;try{let i=await O.post("/api/agent/claim",{taskId:e.id,agentId:o.agentId});if(!i.data.claimed)return;n=i.data.credentials,t=i.data.task}catch{return}let r=T.cyan(e.id.slice(0,8));if(t&&(t.retryCount??0)>0&&t.executionPlanV2!=null){console.log(),console.log(`${U()} ${T.magenta("\u25C6 RESUME")} ${r} ${e.summary.substring(0,60)}`),console.log(`${U()} ${r} Retry #${t.retryCount} with existing plan \u2014 skipping planning`),we.add(e.id);try{await O.post("/api/agent/resume-plan",{taskId:e.id,agentId:o.agentId}),console.log(`${U()} ${r} ${T.green("\u2713")} Resumed with existing plan \u2192 ${T.green("queued")}`)}catch(i){let a=i,l=a.response?.data?.error||a.message||String(i);console.error(`${U()} ${r} ${T.red("\u2717")} Resume failed: ${l}`)}we.delete(e.id);return}console.log(),console.log(`${U()} ${T.magenta("\u25C6 PLANNING")} ${r} ${e.summary.substring(0,60)}`),we.add(e.id),Do(e,o,n).then(i=>{console.log(i?`${U()} ${T.green("\u2713")} Planning complete for ${r}`:`${U()} ${T.red("\u2717")} Planning failed for ${r}`)}).catch(i=>console.error(`${U()} ${T.red("\u2717")} Planning error for ${r}:`,i.message||i)).finally(()=>we.delete(e.id))}async function mn(e,o){if(co()>=o.maxWorkers)return;let t;try{if(t=(await O.post("/api/agent/claim",{taskId:e.id,agentId:o.agentId})).data,!t.claimed)return}catch{return}try{await O.post("/api/agent/started",{taskId:e.id,agentId:o.agentId})}catch{let f=T.cyan(e.id.slice(0,8));console.warn(`${U()} ${T.yellow("\u26A0")} Failed to report started for ${f}`)}let r=T.cyan(e.id.slice(0,8));console.log(),console.log(`${U()} ${T.blue("\u25B6 EXECUTING")} ${r} ${e.summary.substring(0,60)}`);let s=await gn(),i=t.task||{id:e.id,summary:e.summary,description:e.description,jiraIssueKey:e.jiraIssueKey,workerModel:e.workerModel,workerProvider:e.workerProvider,githubRepo:e.githubRepo,scmProvider:e.scmProvider,skipManagerReview:e.skipManagerReview,deploymentEnabled:e.deploymentEnabled,improvementEnabled:e.improvementEnabled,qualityGateBypass:e.qualityGateBypass,standardSdkMode:e.standardSdkMode,parentTaskId:e.parentTaskId,taskNotes:e.taskNotes,githubPrUrl:e.githubPrUrl,githubPrNumber:e.githubPrNumber,executionPlanV2:e.executionPlanV2,jiraFields:e.jiraFields||{}},a=t.credentials;Ho(i,o,s,a).catch(l=>console.error(`${U()} ${T.red("\u2717")} Spawn failed for ${r}:`,l.message||l))}async function fn(e,o){let n=T.cyan(e.id.slice(0,8));ye.add(e.id);try{let t=await O.post("/api/agent/claim-manager",{taskId:e.id,agentId:o.agentId,action:e.managerAction});if(!t.data.claimed){ye.delete(e.id);return}let r=t.data.task,s=t.data.credentials||{},i=e.managerAction==="analyze_logs"?T.yellow("LOG ANALYSIS"):T.yellow("PR REVIEW");console.log(),console.log(`${U()} ${T.magenta("\u25C6 MANAGER")} ${i} ${n} ${e.summary.substring(0,60)}`);let a={id:e.id,summary:r?.summary||e.summary,description:r?.description||e.description,jiraIssueKey:r?.jiraIssueKey||e.jiraIssueKey,githubRepo:r?.githubRepo||e.githubRepo,scmProvider:r?.scmProvider||e.scmProvider,githubPrUrl:r?.githubPrUrl||e.githubPrUrl,githubPrNumber:r?.githubPrNumber||e.githubPrNumber,managerAction:e.managerAction};Qo(a,o,s).then(()=>{console.log(`${U()} ${n} ${T.magenta("MGR")} ${T.green("\u2713")} Manager ${e.managerAction} dispatched`)}).catch(l=>{console.error(`${U()} ${n} ${T.magenta("MGR")} ${T.red("\u2717")} Manager spawn failed:`,l.message||l)}).finally(()=>ye.delete(e.id))}catch(t){ye.delete(e.id);let r=t;console.error(`${U()} ${n} ${T.red("\u2717")} Failed to claim manager task:`,r.message||String(t))}}var Ye=null,Je=null;function tt(){Ye&&(clearInterval(Ye),Ye=null),Je&&(clearInterval(Je),Je=null)}function nt(e){console.log(` ${T.dim("Polling every")} ${e.pollIntervalMs/1e3}s ${T.dim("\xB7 waiting for tasks...")}`),ot(e),Ye=setInterval(()=>ot(e),e.pollIntervalMs)}function rt(e){Je=setInterval(async()=>{let o=Yo(),n=Array.from(we),t=Array.from(ye),r=[...o,...n,...t];try{let s=await O.post("/api/agent/heartbeat",{agentId:e.agentId,activeTasks:r,agentVersion:z}),i=s.data?.cancelledTasks;if(i&&i.length>0)for(let w of i)Jo(w);let{updateAvailable:a,updateRequired:l,latestVersion:f}=s.data??{};l&&!He?(He=!0,console.log(`${U()} ${T.red("\u26A0 Agent update required")} (current: ${z}, required: ${f})`),console.log(`${U()} ${T.yellow("Refusing new tasks until updated.")}`),await Re()?qe():(console.log(`${U()} ${T.red("Auto-update failed.")} Run: npm install -g @workermill/agent@latest`),He=!1)):a&&!et&&!l&&(et=!0,console.log(`${U()} ${T.yellow(`Update available: ${f}`)} (current: ${z}). Run: workermill-agent update`))}catch{}},e.heartbeatIntervalMs)}ee();async function uo(e){console.log(),console.log(K.bold.cyan(" WorkerMill Remote Agent")),console.log(K.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(),ko(e.apiUrl,e.apiKey);try{let o=await O.get("/api/agent/config"),n=o.data.maxConcurrentWorkers;n&&typeof n=="number"&&(e.maxWorkers=n),o.data.workerImageUrl&&(e.workerImage=o.data.workerImageUrl),console.log(` ${K.green("\u25CF")} Connected to ${K.cyan(e.apiUrl)}`),console.log(` ${K.dim("Agent:")} ${e.agentId}`),console.log(` ${K.dim("Workers:")} ${K.yellow(String(e.maxWorkers))} parallel`),console.log(` ${K.dim("Image:")} ${e.workerImage}`),console.log(` ${K.dim("SCM:")} ${o.data.scmProvider}`),console.log(` ${K.dim("Model:")} ${K.yellow(o.data.defaultWorkerModel)}`),console.log()}catch(o){let n=o;throw n.response?.status===401?new Error("Authentication failed. Check your API key."):new Error(`Failed to connect to WorkerMill API: ${n.message||String(o)}`)}try{let o=await O.post("/api/agent/register",{agentId:e.agentId,maxWorkers:e.maxWorkers,agentVersion:z}),{updateAvailable:n,updateRequired:t,latestVersion:r}=o.data;t?(console.log(K.red(` \u26A0 Agent update required (current: ${z}, required: ${r})`)),await Re()?qe():console.log(K.yellow(" Auto-update failed. Run: npm install -g @workermill/agent@latest"))):n&&console.log(K.yellow(` Update available: ${r} (current: ${z}). Run: workermill-agent update`))}catch{}return nt(e),rt(e),console.log(K.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(` ${K.green("\u25CF")} Agent is running. ${K.dim("Press Ctrl+C to stop.")}`),console.log(),async()=>{console.log(),console.log(K.dim(" Shutting down...")),tt();try{await O.post("/api/agent/deregister",{agentId:e.agentId})}catch{}await Xo(),console.log(` ${K.red("\u25CF")} Agent stopped.`)}}var hn=typeof process<"u"&&process.argv[1]&&(process.argv[1].endsWith("/index.ts")||process.argv[1].endsWith("/index.js"));if(hn){try{await import("dotenv/config")}catch{}let{loadConfig:e,validatePrerequisites:o}=await Promise.resolve().then(()=>(ee(),$o)),n=e();o(),console.log(K.dim(" Prerequisites validated."));let t=await uo(n);process.on("SIGINT",async()=>{await t(),process.exit(0)}),process.on("SIGTERM",async()=>{await t(),process.exit(0)})}async function go(e){yn(Z())||(console.log(D.red("No configuration found.")),console.log(`Run ${D.cyan("workermill-agent setup")} first.`),process.exit(1));let o=pe(),t=Ze(o.workerImage).filter(d=>!d.ok),r=t.find(d=>d.name==="Worker image"),s=new Set(["Claude CLI","Claude auth"]),i=t.filter(d=>d.name!=="Worker image"&&!s.has(d.name)),a=t.filter(d=>s.has(d.name));if(i.length>0){console.log(D.red("Prerequisites check failed:"));for(let d of i)console.log(D.red(` \u2717 ${d.name}: ${d.detail}`));process.exit(1)}if(a.length>0)for(let d of a)console.log(D.yellow(` \u26A0 ${d.name}: ${d.detail} (required for Anthropic provider)`));if(r){console.log(D.yellow(` Worker image not found locally. Pulling ${o.workerImage}...`));let{spawnSync:d}=await import("child_process");d("docker",["pull",o.workerImage],{stdio:"inherit",timeout:6e5}).status!==0&&(console.log(D.red(" Failed to pull worker image.")),process.exit(1)),console.log(D.green(" \u2713 Worker image pulled"))}if(e.detach){let d=Ee(),y=ie();console.log(D.dim("Starting agent in background...")),console.log(D.dim(` Logs: ${d}`)),console.log(D.dim(` PID: ${y}`));let C=kn(d,"a"),x=wn("workermill-agent",["start"],{detached:!0,stdio:["ignore",C,C],shell:!0});x.pid?(st(y,String(x.pid),"utf-8"),x.unref(),console.log(D.green(`Agent started (PID: ${x.pid})`)),console.log(`Check status with: ${D.cyan("workermill-agent status")}`)):(console.log(D.red("Failed to start agent in background.")),process.exit(1));return}let l=Ee(),f=An(l,{flags:"a"}),w=process.stdout.write.bind(process.stdout),g=process.stderr.write.bind(process.stderr);process.stdout.write=(d,...y)=>(f.write(d),w(d,...y)),process.stderr.write=(d,...y)=>(f.write(d),g(d,...y)),console.log(),console.log(D.bold.cyan(" WorkerMill Remote Agent")),console.log(D.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log();let h=Math.round($n()/(1024*1024*1024));h<8?(console.log(D.red(` \u2717 Insufficient RAM: ${h} GB (minimum 8 GB, recommended 16 GB)`)),process.exit(1)):h<16&&console.log(D.yellow(` \u26A0 RAM: ${h} GB (below recommended 16 GB \u2014 workers may be slow)`));let P=Ne();console.log(D.dim(` Agent: ${o.agentId}`)),console.log(D.dim(` Version: ${z}`)),console.log(D.dim(` Image: ${o.workerImage}`)),console.log();try{let d=await uo(o);st(ie(),String(process.pid),"utf-8");let y=!1,C=async()=>{y&&(console.log(D.red(`
26
+ Force exit.`)),process.exit(1)),y=!0;let x=setTimeout(()=>{console.log(D.red(`
27
+ Cleanup timed out. Force exit.`)),process.exit(1)},1e4);x.unref(),await d(),clearTimeout(x);try{In(ie())}catch{}process.exit(0)};process.on("SIGINT",C),process.on("SIGTERM",C)}catch(d){console.log(D.red(`Failed to start: ${d instanceof Error?d.message:String(d)}`)),process.exit(1)}}ee();import Ie from"chalk";import{existsSync as bn,readFileSync as En,unlinkSync as po}from"fs";async function it(){let e=ie();if(!bn(e)){console.log(Ie.yellow("No running agent found (no PID file).")),console.log(Ie.dim(`Expected PID file at: ${e}`));return}let o=En(e,"utf-8").trim(),n=parseInt(o,10);if(isNaN(n)){console.log(Ie.red(`Invalid PID in ${e}: ${o}`)),po(e);return}try{process.kill(n,0)}catch{console.log(Ie.yellow(`Agent (PID ${n}) is not running. Cleaning up PID file.`)),po(e);return}console.log(Ie.dim(`Stopping agent (PID ${n})...`));try{process.kill(n,"SIGTERM")}catch(r){console.log(Ie.red(`Failed to stop agent: ${r instanceof Error?r.message:String(r)}`));return}let t=Date.now();for(;Date.now()-t<15e3;)try{process.kill(n,0),await new Promise(r=>setTimeout(r,500))}catch{break}try{po(e)}catch{}console.log(Ie.green("Agent stopped."))}ee();import j from"chalk";import{existsSync as at,readFileSync as Sn}from"fs";import{execSync as Tn}from"child_process";import Cn from"axios";async function lt(){if(console.log(),console.log(j.bold("WorkerMill Remote Agent Status")),console.log(j.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(),!at(Z())){console.log(j.yellow(" Not configured. Run 'workermill-agent setup' first."));return}let e=pe(),o=ie(),n=!1,t=null;if(at(o)){let s=Sn(o,"utf-8").trim();if(t=parseInt(s,10),!isNaN(t))try{process.kill(t,0),n=!0}catch{n=!1}}let r=n?j.green("\u25CF online"):j.red("\u25CF offline");console.log(` Status: ${r}${t?j.dim(` (PID ${t})`):""}`),console.log(` Agent ID: ${e.agentId}`),console.log(` API URL: ${e.apiUrl}`),console.log(` Max workers: ${e.maxWorkers}`),console.log(` Image: ${e.workerImage}`),console.log(),console.log(j.bold(" Active Containers"));try{let s=Tn('docker ps --filter "name=workermill-" --format "{{.Names}} {{.Status}} {{.RunningFor}}"',{encoding:"utf-8",timeout:1e4}).trim();if(s){let i=s.split(`
28
+ `);console.log(j.dim(` Found ${i.length} container(s):`));for(let a of i){let[l,f,w]=a.split(" ");console.log(` ${j.cyan(l)} ${f} ${j.dim(w||"")}`)}}else console.log(j.dim(" No active containers"))}catch{console.log(j.dim(" Could not query Docker"))}console.log(),console.log(j.bold(" API Connectivity"));try{let s=await Cn.get(`${e.apiUrl}/api/agent/config`,{headers:{"x-api-key":e.apiKey},timeout:1e4});console.log(` ${j.green("\u2713")} Connected to ${e.apiUrl}`),console.log(j.dim(` SCM: ${s.data.scmProvider}, Model: ${s.data.defaultWorkerModel}`))}catch(s){s.response?.status===401?console.log(` ${j.red("\u2717")} Authentication failed (invalid API key)`):console.log(` ${j.red("\u2717")} Cannot reach ${e.apiUrl}`)}console.log()}ee();import Xe from"chalk";import{existsSync as Rn,readFileSync as _n,watchFile as Pn,statSync as ct,openSync as xn,readSync as vn,closeSync as Mn}from"fs";async function dt(e){let o=Ee();if(!Rn(o)){console.log(Xe.yellow("No log file found.")),console.log(Xe.dim(`Expected at: ${o}`)),console.log(Xe.dim("Start the agent with --detach to generate logs."));return}let n=parseInt(e.lines||"50",10),r=_n(o,"utf-8").split(`
29
+ `),s=Math.max(0,r.length-n),i=r.slice(s).join(`
30
+ `);i.trim()&&(process.stdout.write(i),i.endsWith(`
31
+ `)||process.stdout.write(`
32
+ `)),console.log(Xe.dim(`\u2500\u2500 Following ${o} (Ctrl+C to stop) \u2500\u2500`));let a=ct(o).size;Pn(o,{interval:500},()=>{try{let l=ct(o).size;if(l<=a){a=l;return}let f=xn(o,"r"),w=Buffer.alloc(l-a);vn(f,w,0,w.length,a),Mn(f),process.stdout.write(w.toString()),a=l}catch{}}),await new Promise(()=>{})}ee();import _e from"chalk";import{spawnSync as Ln}from"child_process";import{existsSync as Nn}from"fs";async function ut(){let e="public.ecr.aws/a7k5r0v0/workermill-worker:latest";Nn(Z())&&(e=pe().workerImage||e),console.log(),console.log(_e.dim(` Pulling worker image: ${e}`)),console.log(_e.dim(" This may take a few minutes...")),console.log();let o=Ln("docker",["pull",e],{stdio:"inherit",timeout:6e5});console.log(),o.status===0?console.log(_e.green(" \u2713 Worker image updated")):(console.log(_e.red(" \u2717 Failed to pull worker image.")),o.error&&console.log(_e.yellow(` Error: ${o.error.message}`)),console.log(_e.yellow(" Is Docker Desktop running?")),process.exit(1))}import Me from"chalk";async function gt(){console.log(),console.log(Me.bold.cyan(" WorkerMill Agent Update")),console.log(Me.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(` ${Me.dim("Current version:")} ${z}`),console.log(),await Re()?(console.log(),console.log(Me.green(" Update complete. If the agent is running, restart it to use the new version."))):(console.log(),console.error(Me.red(" Update failed. Try running manually: npm install -g @workermill/agent@latest")),process.exitCode=1)}ee();var ce=new Fn;ce.name("workermill-agent").description("WorkerMill Remote Agent - Run AI workers locally with your Claude Max subscription").version(z);ce.command("setup").description("Interactive setup wizard - configure API key, validate prerequisites, pull worker image").action(no);ce.command("start").description("Start the agent, polling the cloud API for tasks").option("--detach","Run in background (daemon mode)").action(go);ce.command("stop").description("Stop the running agent").action(it);ce.command("status").description("Show agent status, active containers, and API connectivity").action(lt);ce.command("logs").description("Live tail of agent logs (like tail -f)").option("-n, --lines <count>","Number of lines to show initially","50").action(dt);ce.command("pull").description("Pull the latest worker Docker image").action(ut);ce.command("update").description("Update the agent to the latest version").action(gt);process.argv.length<=2?On(Z())?go({detach:!1}):no():ce.parse();
package/dist/index.js CHANGED
@@ -1,123 +1,23 @@
1
- /**
2
- * WorkerMill Remote Agent
3
- *
4
- * Importable module for starting the agent programmatically.
5
- * Can also be run directly via `bin/remote-agent` (backward compat with dotenv).
6
- */
7
- import chalk from "chalk";
8
- import { initApi, api } from "./api.js";
9
- import { startPolling, startHeartbeat, stopPolling } from "./poller.js";
10
- import { stopAll } from "./spawner.js";
11
- import { AGENT_VERSION } from "./version.js";
12
- import { selfUpdate, restartAgent } from "./updater.js";
13
- export { loadConfig, loadConfigFromFile, validatePrerequisites, getSystemInfo, findClaudePath } from "./config.js";
14
- /**
15
- * Start the remote agent with the given config.
16
- * Returns a cleanup function to stop the agent.
17
- */
18
- export async function startAgent(config) {
19
- console.log();
20
- console.log(chalk.bold.cyan(" WorkerMill Remote Agent"));
21
- console.log(chalk.dim(" ─────────────────────────────────────"));
22
- console.log();
23
- // Initialize API client
24
- initApi(config.apiUrl, config.apiKey);
25
- // Verify connectivity
26
- try {
27
- const configResponse = await api.get("/api/agent/config");
28
- // Override maxWorkers from cloud settings if available
29
- const cloudMaxWorkers = configResponse.data.maxConcurrentWorkers;
30
- if (cloudMaxWorkers && typeof cloudMaxWorkers === "number") {
31
- config.maxWorkers = cloudMaxWorkers;
32
- }
33
- console.log(` ${chalk.green("●")} Connected to ${chalk.cyan(config.apiUrl)}`);
34
- console.log(` ${chalk.dim("Agent:")} ${config.agentId}`);
35
- console.log(` ${chalk.dim("Workers:")} ${chalk.yellow(String(config.maxWorkers))} parallel`);
36
- console.log(` ${chalk.dim("Image:")} ${config.workerImage}`);
37
- console.log(` ${chalk.dim("SCM:")} ${configResponse.data.scmProvider}`);
38
- console.log(` ${chalk.dim("Model:")} ${chalk.yellow(configResponse.data.defaultWorkerModel)}`);
39
- console.log();
40
- }
41
- catch (error) {
42
- const err = error;
43
- if (err.response?.status === 401) {
44
- throw new Error("Authentication failed. Check your API key.");
45
- }
46
- else {
47
- throw new Error(`Failed to connect to WorkerMill API: ${err.message || String(error)}`);
48
- }
49
- }
50
- // Register agent
51
- try {
52
- const registerResponse = await api.post("/api/agent/register", {
53
- agentId: config.agentId,
54
- maxWorkers: config.maxWorkers,
55
- agentVersion: AGENT_VERSION,
56
- });
57
- const { updateAvailable, updateRequired, latestVersion } = registerResponse.data;
58
- if (updateRequired) {
59
- console.log(chalk.red(` ⚠ Agent update required (current: ${AGENT_VERSION}, required: ${latestVersion})`));
60
- const success = await selfUpdate();
61
- if (success) {
62
- restartAgent();
63
- }
64
- else {
65
- console.log(chalk.yellow(" Auto-update failed. Run: npm install -g @workermill/agent@latest"));
66
- }
67
- }
68
- else if (updateAvailable) {
69
- console.log(chalk.yellow(` Update available: ${latestVersion} (current: ${AGENT_VERSION}). Run: workermill-agent update`));
70
- }
71
- }
72
- catch {
73
- // Registration is best-effort, don't fail startup
74
- }
75
- // Start polling and heartbeat loops
76
- startPolling(config);
77
- startHeartbeat(config);
78
- console.log(chalk.dim(" ─────────────────────────────────────"));
79
- console.log(` ${chalk.green("●")} Agent is running. ${chalk.dim("Press Ctrl+C to stop.")}`);
80
- console.log();
81
- // Return cleanup function
82
- return async () => {
83
- console.log();
84
- console.log(chalk.dim(" Shutting down..."));
85
- // Stop poll/heartbeat loops first so nothing re-fires during cleanup
86
- stopPolling();
87
- try {
88
- await api.post("/api/agent/deregister", { agentId: config.agentId });
89
- }
90
- catch {
91
- // Best-effort deregister
92
- }
93
- await stopAll();
94
- console.log(` ${chalk.red("●")} Agent stopped.`);
95
- };
96
- }
97
- // Direct execution support (for bin/remote-agent backward compat)
98
- // Only runs when this file is the main module
99
- const isDirectRun = typeof process !== "undefined" &&
100
- process.argv[1] &&
101
- (process.argv[1].endsWith("/index.ts") || process.argv[1].endsWith("/index.js"));
102
- if (isDirectRun) {
103
- // Dynamic import dotenv for backward compat (not a dependency in published package)
104
- try {
105
- await import("dotenv/config");
106
- }
107
- catch {
108
- // dotenv not available in published package — that's fine
109
- }
110
- const { loadConfig, validatePrerequisites: validate } = await import("./config.js");
111
- const config = loadConfig();
112
- validate();
113
- console.log(chalk.dim(" Prerequisites validated."));
114
- const cleanup = await startAgent(config);
115
- process.on("SIGINT", async () => {
116
- await cleanup();
117
- process.exit(0);
118
- });
119
- process.on("SIGTERM", async () => {
120
- await cleanup();
121
- process.exit(0);
122
- });
123
- }
1
+ var vt=Object.defineProperty;var Ct=(e,t)=>()=>(e&&(t=e(e=0)),t);var xt=(e,t)=>{for(var r in t)vt(e,r,{get:t[r],enumerable:!0})};var Ye={};xt(Ye,{checkPrerequisites:()=>Vt,findClaudePath:()=>ie,getConfigDir:()=>Kt,getConfigFile:()=>jt,getLogFile:()=>Bt,getPidFile:()=>Gt,getSystemInfo:()=>Ve,loadConfig:()=>Be,loadConfigFromFile:()=>Ge,saveConfigToFile:()=>Wt,validatePrerequisites:()=>We});import{existsSync as fe,readFileSync as Ot,mkdirSync as Nt,writeFileSync as Lt,chmodSync as Dt}from"fs";import{execSync as oe}from"child_process";import{hostname as je,homedir as ue}from"os";import{join as H}from"path";function Kt(){return pe}function jt(){return de}function Gt(){return Ut}function Bt(){return Ft}function Ge(){fe(de)||(console.error("No config found. Run 'workermill-agent setup' first."),process.exit(1));let e;try{e=Ot(de,"utf-8")}catch{console.error("Failed to read config file:",de),process.exit(1)}let t;try{t=JSON.parse(e)}catch{console.error("Config file is corrupted. Re-run 'workermill-agent setup'."),process.exit(1)}(!t.apiUrl||!t.apiKey)&&(console.error("Config file is missing required fields (apiUrl, apiKey). Re-run 'workermill-agent setup'."),process.exit(1));let r=t.workerImage||"workermill-worker:local";return{apiUrl:t.apiUrl,apiKey:t.apiKey,agentId:t.agentId,maxWorkers:t.maxWorkers||4,pollIntervalMs:t.pollIntervalMs||5e3,heartbeatIntervalMs:t.heartbeatIntervalMs||3e4,githubToken:t.tokens?.github||"",bitbucketToken:t.tokens?.bitbucket||"",gitlabToken:t.tokens?.gitlab||"",workerImage:r}}function Wt(e){fe(pe)||Nt(pe,{recursive:!0}),Lt(de,JSON.stringify(e,null,2),"utf-8");try{Dt(de,384)}catch{}}function Be(){let e=process.env.WORKERMILL_API_URL,t=process.env.WORKERMILL_API_KEY;return e||(console.error("WORKERMILL_API_URL is required in .env.remote"),process.exit(1)),t||(console.error("WORKERMILL_API_KEY is required in .env.remote"),console.error("Get your API key from Settings > Integrations on the WorkerMill dashboard."),process.exit(1)),{apiUrl:e.replace(/\/$/,""),apiKey:t,agentId:process.env.AGENT_ID||`agent-${je()}`,maxWorkers:parseInt(process.env.MAX_WORKERS||"4",10),pollIntervalMs:parseInt(process.env.POLL_INTERVAL_MS||"5000",10),heartbeatIntervalMs:parseInt(process.env.HEARTBEAT_INTERVAL_MS||"30000",10),githubToken:process.env.GITHUB_TOKEN||"",bitbucketToken:process.env.BITBUCKET_TOKEN||"",gitlabToken:process.env.GITLAB_TOKEN||"",workerImage:process.env.WORKER_IMAGE||"workermill-worker:local"}}function ie(){let e=process.platform==="win32",t=e?"where":"which";try{return oe(`${t} claude`,{stdio:"ignore",timeout:1e4}),"claude"}catch{}let r=[];e?r.push(H(process.env.ProgramFiles||"C:\\Program Files","ClaudeCode","claude.exe"),H(process.env.LOCALAPPDATA||"","Programs","ClaudeCode","claude.exe"),H(ue(),"AppData","Local","Programs","ClaudeCode","claude.exe"),H(ue(),".local","bin","claude.exe")):r.push(H(ue(),".local","bin","claude"),"/opt/homebrew/bin/claude","/usr/local/bin/claude");for(let o of r)if(o&&fe(o))return o;return null}function Vt(e){let t=[],r=e||"workermill-worker:local";try{let l=oe("docker version --format {{.Server.Version}}",{encoding:"utf-8",timeout:1e4}).trim();t.push({name:"Docker",ok:!0,detail:l})}catch{t.push({name:"Docker",ok:!1,detail:"Not running or not installed"})}let o=ie();if(o)try{let l=oe(`"${o}" --version`,{encoding:"utf-8",timeout:1e4}).trim();t.push({name:"Claude CLI",ok:!0,detail:l})}catch{t.push({name:"Claude CLI",ok:!0,detail:o})}else t.push({name:"Claude CLI",ok:!1,detail:"Not installed"});let n=ue(),s=H(n,".claude",".credentials.json");fe(s)?t.push({name:"Claude auth",ok:!0,detail:"Credentials found"}):t.push({name:"Claude auth",ok:!1,detail:"Run 'claude' and complete sign-in"});let i=process.version;parseInt(i.slice(1).split(".")[0],10)>=20?t.push({name:"Node.js",ok:!0,detail:i}):t.push({name:"Node.js",ok:!1,detail:`${i} (need >= 20)`});try{oe(`docker image inspect ${r}`,{stdio:"ignore",timeout:1e4}),t.push({name:"Worker image",ok:!0,detail:r})}catch{t.push({name:"Worker image",ok:!1,detail:`'${r}' not found`})}return t}function We(){try{oe("docker version",{stdio:"ignore"})}catch{console.error("Docker is not available. Please install Docker and ensure it's running."),process.exit(1)}let e=process.env.WORKER_IMAGE||"workermill-worker:local";try{oe(`docker image inspect ${e}`,{stdio:"ignore"})}catch{console.error(`Worker image '${e}' not found.`),console.error(e==="workermill-worker:local"?"Build it with: ./bin/local-workermill build-worker":`Pull it with: docker pull ${e}`),process.exit(1)}ie()||(console.error("Claude CLI is not installed."),console.error("Install it: curl -fsSL https://claude.ai/install.sh | bash"),process.exit(1));let t=ue(),r=H(t,".claude",".credentials.json");fe(r)||(console.error("Claude credentials not found."),console.error("Run 'claude' and complete the sign-in flow to authenticate."),process.exit(1))}function Ve(){let e="unknown";try{e=oe("docker version --format {{.Server.Version}}",{encoding:"utf-8",timeout:1e4}).trim()}catch{}let t="unknown",r=ie();if(r)try{t=oe(`"${r}" --version`,{encoding:"utf-8",timeout:1e4}).trim()}catch{}return{hostname:je(),platform:process.platform,nodeVersion:process.version,dockerVersion:e,claudeVersion:t}}var pe,de,Ut,Ft,we=Ct(()=>{"use strict";pe=H(ue(),".workermill"),de=H(pe,"config.json"),Ut=H(pe,"agent.pid"),Ft=H(pe,"agent.log")});import U from"chalk";import Mt from"axios";var Oe=null;function Ke(e,t){Oe=Mt.create({baseURL:e,headers:{"Content-Type":"application/json","x-api-key":t},timeout:3e4})}var C=new Proxy({},{get(e,t){if(!Oe)throw new Error("API client not initialized. Call initApi() first.");return Oe[t]}});import y from"chalk";we();import c from"chalk";import{spawn as fo,execSync as Ue}from"child_process";import{spawn as Qt}from"child_process";import ae from"chalk";import Ie from"axios";var Yt=16384,zt=6e5;async function ze(e,t,r,o,n){let s=n?.maxTokens??Yt,i=n?.temperature??.7,a=n?.timeoutMs??zt;switch(e){case"anthropic":return Ht(t,r,o,s,i,a);case"openai":return Jt(t,r,o,s,i,a);case"google":return qt(t,r,o,s,i,a);case"ollama":return Xt(t,r,o,s,i,a);default:throw new Error(`Unsupported AI provider: ${e}`)}}async function Ht(e,t,r,o,n,s){let a=(await Ie.post("https://api.anthropic.com/v1/messages",{model:e,max_tokens:o,temperature:n,messages:[{role:"user",content:t}]},{headers:{"x-api-key":r,"anthropic-version":"2023-06-01","Content-Type":"application/json"},timeout:s})).data?.content;if(Array.isArray(a))return a.filter(l=>l.type==="text").map(l=>l.text).join("");throw new Error("Unexpected Anthropic API response format")}async function Jt(e,t,r,o,n,s){let a=(await Ie.post("https://api.openai.com/v1/chat/completions",{model:e,max_tokens:o,temperature:n,messages:[{role:"user",content:t}]},{headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"},timeout:s})).data?.choices?.[0]?.message;if(a?.content)return a.content;throw new Error("Unexpected OpenAI API response format")}async function qt(e,t,r,o,n,s){let l=(await Ie.post(`https://generativelanguage.googleapis.com/v1beta/models/${e}:generateContent?key=${r}`,{contents:[{parts:[{text:t}]}],generationConfig:{maxOutputTokens:o,temperature:n}},{headers:{"Content-Type":"application/json"},timeout:s})).data?.candidates?.[0]?.content?.parts?.[0]?.text;if(l)return l;throw new Error("Unexpected Google AI API response format")}async function Xt(e,t,r,o,n,s){let i=r||"http://localhost:11434",l=(await Ie.post(`${i}/api/generate`,{model:e,prompt:t,stream:!1,options:{temperature:n}},{headers:{"Content-Type":"application/json"},timeout:s})).data?.response;if(l)return l;throw new Error("Unexpected Ollama API response format")}var Ee=null;async function Zt(){if(Ee)return Ee;try{let{data:e}=await C.get("/api/agent/critic-prompt");return Ee={promptTemplate:e.promptTemplate,approvalThreshold:e.approvalThreshold??85,maxTargetFiles:e.maxTargetFiles??15},Ee}catch{return console.warn("Failed to fetch critic config from API, using fallback defaults"),{promptTemplate:`Review this execution plan and score it 0-100.
2
+
3
+ ## PRD
4
+ {{PRD}}
5
+
6
+ ## PLAN
7
+ {{PLAN}}
8
+
9
+ Respond with JSON: {"approved": boolean, "score": number, "risks": [], "suggestions": []}`,approvalThreshold:85,maxTargetFiles:15}}}var $e=15,J=85;function Je(e){let t=e.indexOf("```json");if(t!==-1){let o=t+7,n=e.indexOf("{",o);if(n!==-1){let s=He(e,n);if(s)return JSON.parse(s)}}let r=e.indexOf('"stories"');if(r!==-1){let n=e.substring(0,r).lastIndexOf("{");if(n!==-1){let s=He(e,n);if(s)return JSON.parse(s)}}throw new Error("Could not find JSON execution plan in output")}function He(e,t){let r=0,o=!1,n=!1;for(let s=t;s<e.length;s++){let i=e[s];if(n){n=!1;continue}if(i==="\\"){o&&(n=!0);continue}if(i==='"'){o=!o;continue}if(!o){if(i==="{")r++;else if(i==="}"&&(r--,r===0))return e.substring(t,s+1)}}return null}function qe(e){let t=0,r=[];for(let o of e.stories)if(!o.targetFiles||!Array.isArray(o.targetFiles))o.targetFiles=[];else if(o.targetFiles.length>$e){let n=o.targetFiles.slice($e);r.push(`${o.id}: ${o.targetFiles.length} files \u2192 ${$e} (dropped: ${n.join(", ")})`),o.targetFiles=o.targetFiles.slice(0,$e),t++}return{truncatedCount:t,details:r}}function Xe(e,t){if(e.stories.length<=t)return{droppedCount:0,details:[]};let r=e.stories.length-t,n=e.stories.slice(t).map(i=>`${i.id}: "${i.title}" (${i.persona})`);e.stories=e.stories.slice(0,t);let s=new Set(e.stories.map(i=>i.id));for(let i of e.stories)i.dependencies=i.dependencies.filter(a=>s.has(a));return{droppedCount:r,details:n}}function Qe(e){let t=new Map,r=0,o=[];for(let n of e.stories){if(!n.targetFiles||n.targetFiles.length===0)continue;let s=[],i=[];for(let a of n.targetFiles)t.get(a)?i.push(a):(t.set(a,n.id),s.push(a));i.length>0&&(n.targetFiles=s,r+=i.length,o.push(`${n.id}: removed ${i.join(", ")} (owned by ${i.map(a=>t.get(a)).join(", ")})`))}return{resolvedCount:r,details:o}}function Ze(e){return"```json\n"+JSON.stringify(e,null,2)+"\n```"}async function eo(e,t){let r=await Zt();J=r.approvalThreshold,$e=r.maxTargetFiles;let o=JSON.stringify(t,null,2);return r.promptTemplate.replace("{{PRD}}",e).replace("{{PLAN}}",o)}function to(e){let t=e.trim();if(t.includes("```")){let n=t.match(/```(?:json)?\s*([\s\S]*?)```/);n&&(t=n[1].trim())}let r=t.indexOf("{");r>0&&(t=t.substring(r));let o=JSON.parse(t);return{approved:o.approved,score:Math.max(0,Math.min(100,Math.round(o.score))),risks:o.risks||[],suggestions:o.suggestions,storyFeedback:Array.isArray(o.storyFeedback)?o.storyFeedback:void 0}}function oo(e,t,r,o,n){return new Promise((s,i)=>{let a=Qt(e,["--print","--model",t,"--permission-mode","bypassPermissions"],{env:o,stdio:["pipe","pipe","pipe"]});a.stdin.write(r),a.stdin.end();let l="",E="";a.stdout.on("data",d=>{let w=d.toString();l+=w;let P=w.split(`
10
+ `).filter(M=>M.trim());for(let M of P){let R=M.trim().length>200?M.trim().substring(0,200)+"\u2026":M.trim();R&&(n&&ot(n,`${tt} [critic] ${R}`,"output"),console.log(`${Ae()} ${ae.dim("\u{1F50D}")} ${ae.dim(R)}`))}}),a.stderr.on("data",d=>{E+=d.toString()});let A=setTimeout(()=>{a.kill("SIGTERM"),i(new Error("Critic CLI timed out after 20 minutes"))},12e5);a.on("exit",d=>{clearTimeout(A),d!==0?i(new Error(`Critic CLI failed (exit ${d}): ${E.substring(0,300)}`)):s(l)}),a.on("error",d=>{clearTimeout(A),i(d)})})}function et(e){let t=["","## CRITIC FEEDBACK \u2014 Your previous plan was REJECTED","",`Score: ${e.score}/100 (need >= ${J} to pass)`,""];if(e.risks.length>0){t.push("### Risks Identified:");for(let r of e.risks)t.push(`- ${r}`);t.push("")}if(e.suggestions&&e.suggestions.length>0){t.push("### Required Changes:");for(let r of e.suggestions)t.push(`- ${r}`);t.push("")}if(e.storyFeedback&&e.storyFeedback.length>0){t.push("### Per-Story Feedback:");for(let r of e.storyFeedback)if(t.push(`- **${r.storyId}**: ${r.feedback}`),r.suggestedChanges)for(let o of r.suggestedChanges)t.push(` - ${o}`);t.push("")}return t.push("**You MUST address ALL feedback above.** Each story must target at most 5 files.","Stories MUST NOT overlap on targetFiles. Generate a revised plan."),t.join(`
11
+ `)}var tt="[\u{1F5FA}\uFE0F planning_agent \u{1F916}]";function Ae(){return ae.dim(new Date().toLocaleTimeString())}async function ot(e,t,r="system",o="info"){try{await C.post("/api/control-center/logs",{taskId:e,type:r,message:t,severity:o})}catch{}}async function rt(e,t,r,o,n,s,i,a,l){let E=await eo(r,o),A=i||"anthropic";console.log(`${Ae()} ${s} ${ae.dim(`Running critic validation (${A})...`)}`),l&&ot(l,`${tt} Running critic validation (${A})...`);try{let d;if(A==="anthropic")d=await oo(e,t,E,n,l);else{if(!a)throw new Error(`No API key for critic provider "${A}"`);d=await ze(A,t,E,a,{maxTokens:4096,temperature:.3,timeoutMs:12e5})}let w=to(d),P=w.score>=J?ae.green("\u2713"):ae.red("\u2717");return console.log(`${Ae()} ${s} ${P} Critic score: ${w.score}/100 (threshold: ${J})`),w}catch(d){let w=d instanceof Error?d.message:String(d);return console.error(`${Ae()} ${s} ${ae.yellow("\u26A0")} Critic failed: ${w.substring(0,100)}`),null}}import{generateText as ro,tool as Ne,stepCountIs as no}from"ai";import{createOpenAI as nt}from"@ai-sdk/openai";import{createAnthropic as so}from"@ai-sdk/anthropic";import{createGoogleGenerativeAI as io}from"@ai-sdk/google";import{z as re}from"zod";import{execSync as Le}from"child_process";import{readFileSync as ao,existsSync as lo}from"fs";function co(e,t,r){switch(e){case"anthropic":return so({apiKey:r})(t);case"openai":return nt({apiKey:r})(t);case"google":return io({apiKey:r})(t);case"ollama":return nt({baseURL:r||"http://localhost:11434/v1",apiKey:"ollama"})(t);default:throw new Error(`Unsupported AI provider: ${e}`)}}var uo=re.object({pattern:re.string().describe("Glob pattern like '**/*.ts', 'src/**/*.js', 'package.json'")}),po=re.object({path:re.string().describe("File path relative to the working directory"),limit:re.number().optional().describe("Max number of lines to read (default: 500)")}),go=re.object({pattern:re.string().describe("Search pattern (regex supported)"),glob:re.string().optional().describe("File glob to filter (e.g. '*.ts', '*.py')")});function mo(e){return{glob:Ne({description:"Find files matching a glob pattern. Returns file paths relative to the working directory.",inputSchema:uo,execute:async t=>{try{let r=Le(`find . -path './.git' -prune -o -path './node_modules' -prune -o -name '${t.pattern.replace(/\*\*/g,"*")}' -print 2>/dev/null | head -200`,{cwd:e,encoding:"utf-8",timeout:15e3}).trim();return r||Le("find . -path './.git' -prune -o -path './node_modules' -prune -o -type f -print 2>/dev/null | head -500",{cwd:e,encoding:"utf-8",timeout:15e3}).trim()||"No files found"}catch{return"Error running glob search"}}}),read_file:Ne({description:"Read the contents of a file. Returns the file text.",inputSchema:po,execute:async t=>{try{let r=`${e}/${t.path}`.replace(/\/\//g,"/");if(!lo(r))return`File not found: ${t.path}`;let o=ao(r,"utf-8"),n=o.split(`
12
+ `),s=t.limit||500;return n.length>s?n.slice(0,s).join(`
13
+ `)+`
14
+ ... (truncated, ${n.length-s} more lines)`:o}catch(r){return`Error reading file: ${r instanceof Error?r.message:String(r)}`}}}),grep:Ne({description:"Search for a pattern in files. Returns matching lines with file paths and line numbers.",inputSchema:go,execute:async t=>{try{let r=t.glob?`--include='${t.glob}'`:"";return Le(`grep -rn ${r} --exclude-dir=node_modules --exclude-dir=.git '${t.pattern.replace(/'/g,"'\\''")}' . 2>/dev/null | head -100`,{cwd:e,encoding:"utf-8",timeout:15e3}).trim()||"No matches found"}catch{return"No matches found"}}})}}async function st(e){let{provider:t,model:r,apiKey:o,prompt:n,systemPrompt:s,workingDir:i,maxTokens:a=16384,temperature:l=.7,timeoutMs:E=6e5,maxSteps:A=15,enableTools:d=!0}=e,w=co(t,r,o),P=d&&i?mo(i):void 0,M=new AbortController,R=setTimeout(()=>M.abort(),E);try{return(await ro({model:w,prompt:n,system:s,maxOutputTokens:a,temperature:l,tools:P,stopWhen:P?no(A):void 0,abortSignal:M.signal})).text}finally{clearTimeout(R)}}function $o(e,t){let r=[e.usage,e.message?.usage,e.result?.usage];for(let o of r)if(o&&typeof o=="object"){let n=o;typeof n.input_tokens=="number"&&(t.inputTokens=Math.max(t.inputTokens,n.input_tokens)),typeof n.output_tokens=="number"&&(t.outputTokens=Math.max(t.outputTokens,n.output_tokens)),typeof n.cache_creation_input_tokens=="number"&&(t.cacheCreationTokens=Math.max(t.cacheCreationTokens,n.cache_creation_input_tokens)),typeof n.cache_read_input_tokens=="number"&&(t.cacheReadTokens=Math.max(t.cacheReadTokens,n.cache_read_input_tokens))}}async function it(e,t,r,o){if(!(t.inputTokens===0&&t.outputTokens===0))try{await C.post(`/api/tasks/${e}/usage/partial`,{inputTokens:t.inputTokens,outputTokens:t.outputTokens,cacheCreationTokens:t.cacheCreationTokens,cacheReadTokens:t.cacheReadTokens,model:r,mode:o})}catch{}}var Z=3;function f(){return c.dim(new Date().toLocaleTimeString())}var ge=[],ne=null;async function lt(){for(;ge.length>0;){let e=ge.splice(0,50);try{await C.post("/api/control-center/logs/batch",{entries:e},{timeout:5e3})}catch{}}}async function _(e,t,r="system",o="info"){ge.length>=200&&ge.shift(),ge.push({taskId:e,message:t,type:r,severity:o}),ne||(ne=lt().finally(()=>{ne=null}))}async function ho(){ne&&await ne,ge.length>0&&(ne=lt().finally(()=>{ne=null}),await ne)}async function me(e,t,r,o,n,s){try{await C.post("/api/agent/planning-progress",{taskId:e,phase:t,elapsedSeconds:r,detail:o,charsGenerated:n,toolCallCount:s})}catch{}}var h="[\u{1F5FA}\uFE0F planning_agent \u{1F916}]";function Te(e){let t=Math.floor(e/60),r=e%60;return t>0?`${t}m ${r}s`:`${r}s`}function at(e,t){switch(e){case"initializing":return`${h} Starting planning agent...`;case"reading_repo":return`${h} Reading repository structure...`;case"analyzing":return`${h} Analyzing requirements...`;case"generating_plan":return`${h} Planning in progress \u2014 analyzing requirements and decomposing into steps (${Te(t)} elapsed)`;case"validating":return`${h} Validating plan...`;case"complete":return`${h} Planning complete`}}function yo(e,t,r,o,n,s,i){let a=c.cyan(n.slice(0,8));return new Promise((l,E)=>{let d=fo(e,["--print","--verbose","--output-format","stream-json","--model",t,"--permission-mode","bypassPermissions"],{cwd:i,env:o,stdio:["pipe","pipe","pipe"]});d.stdin.write(r),d.stdin.end();let w="",P="",M="",R=0,O=0,j={inputTokens:0,outputTokens:0,cacheCreationTokens:0,cacheReadTokens:0},F=t,I="";function L(k=!1){if(!I)return;let B=I.split(`
15
+ `),te=k?"":B.pop()||"";for(let W of B)if(W.trim()){_(n,`${h} ${W}`,"output");let v=W.trim().length>160?W.trim().substring(0,160)+"\u2026":W.trim();console.log(`${f()} ${a} ${c.dim("\u{1F4AD}")} ${c.dim(v)}`)}I=te}let $="initializing",D=!1,g={started:!0,reading:!1,analyzing:!1,generating:!1};function T(k){if(k===$)return;$=k;let B=Math.round((Date.now()-s)/1e3),te=at(k,B);_(n,te),console.log(`${f()} ${a} ${c.dim(te)}`)}let b=setInterval(()=>L(),500),Q=setInterval(()=>{let k=Math.round((Date.now()-s)/1e3);me(n,$,k,at($,k),R,O)},2e3),K=0,Y=setInterval(()=>{let k=Math.round((Date.now()-s)/1e3);if($==="initializing"&&k>=5?T("reading_repo"):$==="reading_repo"&&k>=15&&!D&&T("analyzing"),$==="generating_plan"&&k-K>=30){K=k;let B=`${h} Planning in progress \u2014 analyzing requirements and decomposing into steps (${Te(k)} elapsed)`;_(n,B),console.log(`${f()} ${a} ${c.dim(B)}`)}},5e3),q="";d.stdout.on("data",k=>{q+=k.toString();let B=q.split(`
16
+ `);q=B.pop()||"";for(let te of B){let W=te.trim();if(W)try{let v=JSON.parse(W);if(v.type==="assistant"&&v.message?.content){let p=v.message.content;if(Array.isArray(p))for(let m of p)m.type==="text"&&m.text?(w+=m.text,R+=m.text.length,I+=m.text,D||(D=!0,O>0&&!g.analyzing&&(T("analyzing"),g.analyzing=!0)),R>500&&!g.generating&&(T("generating_plan"),g.generating=!0,K=Math.round((Date.now()-s)/1e3))):m.type==="tool_use"&&(O++,g.reading||(T("reading_repo"),g.reading=!0));else typeof p=="string"&&p&&(w+=p,R+=p.length,I+=p)}else v.type==="content_block_delta"&&v.delta?.text?(w+=v.delta.text,R+=v.delta.text.length,I+=v.delta.text,D||(D=!0,O>0&&!g.analyzing&&(T("analyzing"),g.analyzing=!0)),R>500&&!g.generating&&(T("generating_plan"),g.generating=!0,K=Math.round((Date.now()-s)/1e3))):v.type==="content_block_start"&&v.content_block?.type==="tool_use"?(O++,g.reading||(T("reading_repo"),g.reading=!0)):v.type==="result"&&v.result&&(P=typeof v.result=="string"?v.result:"");if($o(v,j),v.type==="result"&&v.total_cost_usd!==void 0&&v.modelUsage&&typeof v.modelUsage=="object"){let p=Object.keys(v.modelUsage);p.length>0&&(F=p[0])}}catch{w+=W+`
17
+ `,R+=W.length}}}),d.stderr.on("data",k=>{M+=k.toString()});let ye=setInterval(()=>{(j.inputTokens>0||j.outputTokens>0)&&it(n,j,F,"greatest").catch(()=>{})},3e4);function G(){clearInterval(Y),clearInterval(Q),clearInterval(b),clearInterval(ye),L(!0)}let ee=setTimeout(()=>{G(),d.kill("SIGTERM"),E(new Error("Claude CLI timed out after 20 minutes"))},12e5);d.on("exit",k=>{clearTimeout(ee),G();let B=Math.round((Date.now()-s)/1e3);me(n,"validating",B,"Validating plan...",R,O),it(n,j,F,"greatest").catch(()=>{}),k!==0?E(new Error(`Claude CLI failed (exit ${k}): ${M.substring(0,300)}`)):l(P||w)}),d.on("error",k=>{clearTimeout(ee),G(),E(k)})})}function wo(e,t){if(t)switch(e){case"anthropic":return t.anthropicApiKey;case"openai":return t.openaiApiKey;case"google":return t.googleApiKey;case"ollama":return t.ollamaBaseUrl||"http://localhost:11434";default:return}}function Io(e,t,r){switch(r){case"bitbucket":return`https://x-token-auth:${t}@bitbucket.org/${e}.git`;case"gitlab":return`https://oauth2:${t}@gitlab.com/${e}.git`;default:return`https://x-access-token:${t}@github.com/${e}.git`}}async function Eo(e,t,r,o){let n=c.cyan(o.slice(0,8)),s=`/tmp/workermill-planning-${o.slice(0,8)}-${Date.now()}`;try{let i=Io(e,t,r);return console.log(`${f()} ${n} ${c.dim("Cloning repo for planner...")}`),Ue(`git clone --depth 1 --single-branch "${i}" "${s}"`,{stdio:"ignore",timeout:6e4}),console.log(`${f()} ${n} ${c.green("\u2713")} Repo cloned to ${c.dim(s)}`),s}catch(i){let a=i instanceof Error?i.message:String(i);console.error(`${f()} ${n} ${c.yellow("\u26A0")} Clone failed, planner will run without repo access: ${a.substring(0,100)}`);try{Ue(`rm -rf "${s}"`,{stdio:"ignore"})}catch{}return null}}async function ct(e,t,r){let o=c.cyan(e.id.slice(0,8));console.log(`${f()} ${o} Fetching planning prompt...`),await _(e.id,`${h} Fetching planning prompt from cloud API...`);let n=await C.get("/api/agent/planning-prompt",{params:{taskId:e.id}}),{prompt:s,model:i,provider:a,maxStories:l}=n.data,E=typeof l=="number"?l:8,A=i,d=a||"anthropic",w=d==="anthropic",P=process.env.CLAUDE_CLI_PATH||ie()||"claude",M={...process.env};delete M.CLAUDE_CODE_OAUTH_TOKEN;let R=wo(d,r),O=Date.now(),j=e.description||e.summary,F=null;if(e.githubRepo){let T=e.scmProvider||"github",b=T==="bitbucket"?t.bitbucketToken:T==="gitlab"?t.gitlabToken:t.githubToken;b?F=await Eo(e.githubRepo,b,T,e.id):console.log(`${f()} ${o} ${c.yellow("\u26A0")} No SCM token for ${T}, planner will run without repo access`)}let I=s,L=null,$=0,D=[],g=0;try{for(let b=1;b<=Z;b++){let Q=Z>1?` (attempt ${b}/${Z})`:"",K=`${d}/${A}`;b>1?(console.log(`${f()} ${o} Running planner${Q} ${c.dim(`(${c.yellow(K)})`)}`),await _(e.id,`${h} Re-planning${Q} using ${K}`)):(console.log(`${f()} ${o} Running planner ${c.dim(`(${c.yellow(K)})`)}`),await _(e.id,`${h} Starting planning agent using ${K}`));let Y;try{if(w)Y=await yo(P,A,I,M,e.id,O,F||void 0);else{if(!R)throw new Error(`No API key available for provider "${d}". Configure it in Settings > Integrations.`);let m=Math.round((Date.now()-O)/1e3);await me(e.id,"generating_plan",m,"Generating plan via AI SDK...",0,0),Y=await st({provider:d,model:A,apiKey:R,prompt:I,workingDir:F||void 0,enableTools:!!F,maxSteps:10});let S=Math.round((Date.now()-O)/1e3);await me(e.id,"validating",S,"Validating plan...",Y.length,0)}}catch(m){let S=Math.round((Date.now()-O)/1e3),z=m instanceof Error?m.message:String(m);return console.error(`${f()} ${o} ${c.red("\u2717")} Failed after ${S}s: ${z.substring(0,100)}`),await _(e.id,`${h} Planning failed after ${Te(S)}: ${z.substring(0,200)}`,"error","error"),!1}let q=Math.round((Date.now()-O)/1e3),ye=w?"Claude CLI":`${d} API`;console.log(`${f()} ${o} ${c.green("\u2713")} ${ye} done ${c.dim(`(${q}s, ${Y.length} chars)`)}`);let G;try{G=Je(Y)}catch(m){let S=m instanceof Error?m.message:String(m);return console.error(`${f()} ${o} ${c.red("\u2717")} Plan parse failed: ${S.substring(0,100)}`),await _(e.id,`${h} Failed to parse execution plan from Claude output: ${S.substring(0,200)}`,"error","error"),await Ao(e.id,Y,t.agentId,o,q)}let{truncatedCount:ee,details:k}=qe(G);if(ee>0){g+=ee;let m=`${h} File cap applied: ${ee} stories truncated to max 5 targetFiles`;console.log(`${f()} ${o} ${c.yellow("\u26A0")} ${m}`),await _(e.id,m);for(let S of k)console.log(`${f()} ${o} ${c.dim(S)}`)}let{droppedCount:B,details:te}=Xe(G,E);if(B>0){let m=`${h} Story cap applied: ${B} stories dropped (max ${E})`;console.log(`${f()} ${o} ${c.yellow("\u26A0")} ${m}`),await _(e.id,m);for(let S of te)console.log(`${f()} ${o} ${c.dim(S)}`)}let{resolvedCount:W,details:v}=Qe(G);if(W>0){let m=`${h} File overlap resolved: ${W} shared file(s) de-duped across stories`;console.log(`${f()} ${o} ${c.yellow("\u26A0")} ${m}`),await _(e.id,m);for(let S of v)console.log(`${f()} ${o} ${c.dim(S)}`)}console.log(`${f()} ${o} Plan: ${c.bold(G.stories.length)} stories (max ${E})`),await _(e.id,`${h} Plan generated: ${G.stories.length} stories (${Te(q)}). Running critic validation...`);let p=await rt(P,A,j,G,M,o,d,R,e.id);if(p&&p.score>$?(L=G,$=p.score):!p&&!L&&(L=G),p&&D.push({iteration:b,score:p.score,approved:p.approved||p.score>=J,risks:p.risks,suggestions:p.suggestions,filesCapApplied:ee>0?ee:void 0}),!p){let m=`${h} \u26A0\uFE0F CRITIC BYPASSED \u2014 Critic validation failed (timeout/parse error). Posting plan WITHOUT quality gate.`;console.log(`${f()} ${o} ${c.yellow("\u26A0")} ${m}`),await _(e.id,m,"error","warning");let S=Date.now()-O;return await De(e.id,G,t.agentId,o,q,void 0,void 0,D,g,S,b)}if(p.approved||p.score>=J){let m=`${h} Critic approved (score: ${p.score}/100)`;if(console.log(`${f()} ${o} ${c.green("\u2713")} ${m}`),await _(e.id,m),p.risks.length>0){let z=`${h} Critic risks (non-blocking): ${p.risks.join("; ")}`;console.log(`${f()} ${o} ${c.dim(z)}`),await _(e.id,z)}let S=Date.now()-O;return await De(e.id,G,t.agentId,o,q,p.score,p.risks,D,g,S,b)}if(b<Z){let m=et(p);I=s+`
18
+
19
+ `+m;let S=`${h} Critic rejected (score: ${p.score}/100, threshold: ${J}). Re-planning with feedback...`;if(console.log(`${f()} ${o} ${c.yellow("\u26A0")} ${S}`),await _(e.id,S),p.risks.length>0){let z=`${h} Critic risks: ${p.risks.join("; ")}`;console.log(`${f()} ${o} ${c.dim(z)}`),await _(e.id,z)}if(p.suggestions&&p.suggestions.length>0){let z=`${h} Critic suggestions: ${p.suggestions.join("; ")}`;console.log(`${f()} ${o} ${c.dim(z)}`),await _(e.id,z)}}else{let m=`${h} Critic rejected after ${Z} iterations (best score: ${$}/100, threshold: ${J})`;if(console.error(`${f()} ${o} ${c.red("\u2717")} ${m}`),await _(e.id,m,"error","error"),p.risks.length>0){let S=`${h} Final risks: ${p.risks.join("; ")}`;console.error(`${f()} ${o} ${S}`),await _(e.id,S,"error","error")}if(p.suggestions&&p.suggestions.length>0){let S=`${h} Suggestions: ${p.suggestions.join("; ")}`;console.error(`${f()} ${o} ${S}`),await _(e.id,S,"error","error")}}}let T=50;if(L&&$>=T){let b=Math.round((Date.now()-O)/1e3),Q=`${h} Best-plan fallback: posting plan with score ${$}/100 (below ${J} threshold, above ${T} minimum)`;console.log(`${f()} ${o} ${c.yellow("\u26A0")} ${Q}`),await _(e.id,Q);let K=Date.now()-O;if(await De(e.id,L,t.agentId,o,b,$,[`Best-plan fallback: critic rejected after ${Z} iterations`],D,g,K,Z))return!0;console.log(`${f()} ${o} ${c.yellow("\u26A0")} ${h} Fallback post rejected by server, reporting plan-failed`),await _(e.id,`${h} Fallback plan rejected by server \u2014 reporting failure`)}try{let b=L&&$>=T?`Best-plan fallback rejected by server after ${Z} iterations (best score: ${$}/100)`:`Critic rejected after ${Z} iterations (best score: ${$}/100, threshold: ${J}, fallback minimum: ${T})`;await C.post("/api/agent/plan-failed",{taskId:e.id,agentId:t.agentId,reason:b,criticHistory:D})}catch{}return!1}finally{if(await ho(),F)try{Ue(`rm -rf "${F}"`,{stdio:"ignore"})}catch{}}}async function De(e,t,r,o,n,s,i,a,l,E,A){let d=Ze(t);try{let P=(await C.post("/api/agent/plan-result",{taskId:e,rawOutput:d,agentId:r,criticScore:s,criticRisks:i,criticHistory:a,criticIterations:A,fileCapTruncations:l,planningDurationMs:E})).data.storyCount;return console.log(`${f()} ${o} ${c.green("\u2713")} Plan validated: ${c.bold(P)} stories \u2192 ${c.green("queued")}`),await _(e,`${h} Plan validated: ${P} stories. Task queued for execution.`),await me(e,"complete",n,"Planning complete",0,0),!0}catch(w){let P=w,M=P.response?.data?.error||P.response?.data?.detail||String(w),R=P.response?.status?` (${P.response.status})`:"";return console.error(`${f()} ${o} ${c.red("\u2717")} Server validation failed${R}: ${M.substring(0,100)}`),await _(e,`${h} Server-side plan validation failed${R}: ${M.substring(0,200)}`,"error","error"),!1}}async function Ao(e,t,r,o,n){try{let i=(await C.post("/api/agent/plan-result",{taskId:e,rawOutput:t,agentId:r})).data.storyCount;return console.log(`${f()} ${o} ${c.green("\u2713")} Plan validated (server-side): ${c.bold(i)} stories \u2192 ${c.green("queued")}`),await _(e,`${h} Plan validated: ${i} stories. Task queued for execution.`),await me(e,"complete",n,"Planning complete",0,0),!0}catch(s){let a=s.response?.data?.detail||String(s);return console.error(`${f()} ${o} ${c.red("\u2717")} Validation failed: ${a.substring(0,100)}`),await _(e,`${h} Plan validation failed: ${a.substring(0,200)}`,"error","error"),!1}}import u from"chalk";import{spawn as dt,execSync as _e}from"child_process";import*as be from"path";import*as X from"fs";import*as Re from"os";function x(){return u.dim(new Date().toLocaleTimeString())}var V=new Map;function To(){if(process.env.WSL_DISTRO_NAME||process.env.WSL_INTEROP)return!0;try{return X.readFileSync("/proc/version","utf-8").toLowerCase().includes("microsoft")}catch{return!1}}var Se=To(),pt=Se||process.platform==="darwin"||process.platform==="win32";function gt(){if(!Se)return null;try{let e=_e("hostname -I",{encoding:"utf-8"}).trim().split(/\s+/)[0];if(e&&/^\d+\.\d+\.\d+\.\d+$/.test(e))return e}catch{}return null}function mt(e){if(!Se)return e;let t=e.match(/^\/mnt\/([a-zA-Z])\/(.*)$/);return t?`${t[1].toUpperCase()}:/${t[2]}`:e}function ft(){let e=be.join(Re.homedir(),".claude");if(X.existsSync(e))return e;if(Se){let t="/mnt/c/Users";if(X.existsSync(t))try{for(let r of X.readdirSync(t)){if(["Public","Default","Default User","All Users"].includes(r))continue;let o=be.join(t,r,".claude");if(X.existsSync(o))return o}}catch{}}return null}var ut=0;function $t(e){let t=e.match(/^(\d+\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com)/);return t?t[1]:null}function bo(e){let t=e.match(/\.ecr\.([a-z0-9-]+)\.amazonaws\.com/);return t?t[1]:"us-east-1"}function ht(e){if(Date.now()<ut)return!0;let t=bo(e);try{return _e(`aws ecr get-login-password --region ${t} | docker login --username AWS --password-stdin ${e}`,{stdio:"pipe",timeout:3e4}),ut=Date.now()+660*60*1e3,!0}catch{return!1}}function yt(e,t,r){if(r?.scmToken)return r.scmToken;switch(e){case"bitbucket":return t.bitbucketToken;case"gitlab":return t.gitlabToken;default:return t.githubToken}}function _o(e){let t=e.jiraFields;if(!t)return!1;let r=t.labels;if(Array.isArray(r)&&r.some(s=>typeof s=="string"&&s.toLowerCase()==="self-review"))return!0;let n=t.issue?.labels;return!!(Array.isArray(n)&&n.some(s=>typeof s=="string"?s.toLowerCase()==="self-review":s&&typeof s=="object"&&"name"in s?(s.name||"").toLowerCase()==="self-review":!1))}async function wt(e,t,r,o){let n=u.cyan(e.id.slice(0,8));if(V.has(e.id)){console.log(`${x()} ${n} ${u.dim("Already running, skipping")}`);return}let s=`workermill-${e.id.slice(0,8)}`,a=["run","--rm",...!t.workerImage.includes("/")?[]:["--pull","always"],"--name",s],l=Math.round(Re.totalmem()/(1024*1024*1024));if(l<=16?a.push("--memory","6g","--memory-swap","10g","--cpus","2"):(l<=32,a.push("--memory","6g","--memory-swap","12g","--cpus","4")),pt){let g=gt();a.push(`--add-host=host.docker.internal:${g||"host-gateway"}`)}else a.push("--network","host");let E=e.workerProvider||"anthropic",A=ft();if(!A&&E==="anthropic"){console.error(`${x()} ${n} ${u.red("\u2717")} Claude credentials not found. Run 'claude' and complete the sign-in flow.`);return}if(A){let g=be.join(A,".credentials.json");try{X.chmodSync(g,438)}catch{}let T=mt(A);a.push("-v",`${T}:/home/worker/.claude`)}else console.log(`${x()} ${n} ${u.dim("Skipping Claude mount (non-Anthropic worker)")}`);let d=t.apiUrl.replace(/localhost|127\.0\.0\.1/,"host.docker.internal"),w=e.scmProvider||"github",P=yt(w,t,o),M=o?.githubToken||t.githubToken,R=w==="bitbucket"&&o?.scmToken||t.bitbucketToken,O=w==="gitlab"&&o?.scmToken||t.gitlabToken,j={NODE_OPTIONS:"--max-old-space-size=3072",EPIC_MODE:"true",EXECUTION_MODE:"local",TASK_ID:e.id,ORG_ID:e.orgId||"",JIRA_ISSUE_KEY:e.jiraIssueKey||"",JIRA_SUMMARY:e.summary||"",JIRA_DESCRIPTION:e.description||"",TASK_SUMMARY:e.summary||"",TASK_DESCRIPTION:e.description||"",WORKER_PERSONA:e.workerPersona||"",RETRY_NUMBER:String(e.retryCount??0),TICKET_KEY:e.jiraIssueKey||"",API_BASE_URL:d,ORG_API_KEY:t.apiKey,SCM_PROVIDER:w,SCM_TOKEN:P,SCM_BASE_URL:o?.scmBaseUrl||"",GITHUB_TOKEN:M,GH_TOKEN:M,GITHUB_REVIEWER_TOKEN:o?.githubReviewerToken||"",BITBUCKET_TOKEN:R,BITBUCKET_USERNAME:o?.bitbucketUsername||"x-token-auth",GITLAB_TOKEN:O,TARGET_REPO:e.githubRepo||"",GITHUB_REPO:e.githubRepo||"",WORKER_MODEL:e.workerModel||String(r.defaultWorkerModel||""),CLAUDE_MODEL:e.workerModel||String(r.defaultWorkerModel||""),JIRA_BASE_URL:o?.jiraBaseUrl||"",JIRA_EMAIL:o?.jiraEmail||"",JIRA_API_TOKEN:o?.jiraApiToken||"",TICKET_SYSTEM:o?.issueTrackerProvider||"jira",LINEAR_API_KEY:o?.linearApiKey||"",AWS_ACCESS_KEY_ID:o?.customerAwsAccessKeyId||"",AWS_SECRET_ACCESS_KEY:o?.customerAwsSecretAccessKey||"",AWS_DEFAULT_REGION:o?.customerAwsRegion||"",AWS_REGION:o?.customerAwsRegion||"",CUSTOMER_AWS_ROLE_ARN:o?.customerAwsRoleArn||"",CUSTOMER_AWS_EXTERNAL_ID:o?.customerAwsExternalId||"",CUSTOMER_AWS_REGION:o?.customerAwsRegion||"",MANAGER_PROVIDER:o?.managerProvider||"anthropic",MANAGER_MODEL:o?.managerModelId||"",BITBUCKET_EMAIL:o?.bitbucketEmail||"",DEPLOYMENT_ENABLED:e.deploymentEnabled||e.parentTaskId?"true":"false",PRD_CHILD_TASK:e.parentTaskId?"true":"false",IMPROVEMENT_ENABLED:e.improvementEnabled?"true":"false",QUALITY_GATE_BYPASS:e.qualityGateBypass?"true":"false",STANDARD_SDK_MODE:e.standardSdkMode?"true":"false",MAX_REVIEW_REVISIONS:String(r.maxReviewRevisions??3),CODEBASE_INDEXING_ENABLED:r.codebaseIndexingEnabled===!0?"true":"false",EXISTING_PR_URL:e.githubPrUrl||"",EXISTING_PR_NUMBER:e.githubPrNumber?String(e.githubPrNumber):"",PARENT_TASK_ID:e.parentTaskId||e.id,PARENT_JIRA_KEY:e.jiraIssueKey&&/-S\d+$/.test(e.jiraIssueKey)&&e.jiraFields?.parentJiraKey||"",TARGET_BRANCH:e.jiraFields?.targetBranch||"",STORY_BRANCH:e.jiraFields?.storyBranch||"",TASK_NOTES:e.taskNotes||"",TARGET_FILES:JSON.stringify(e.jiraFields?.targetFiles||[]),REFERENCE_FILES:JSON.stringify(e.jiraFields?.referenceFiles||[]),PIPELINE_VERSION:e.jiraFields?.pipelineVersion||e.pipelineVersion||"",V2_STEP_INPUT:e.jiraFields?.v2StepInput?JSON.stringify(e.jiraFields.v2StepInput):"",EXECUTION_MODE_SETTING:e.jiraFields?.executionMode||"autonomous",ANTHROPIC_API_KEY:A?"":o?.anthropicApiKey||process.env.ANTHROPIC_API_KEY||"",WORKER_PROVIDER:e.workerProvider||"anthropic",OPENAI_API_KEY:o?.openaiApiKey||"",GOOGLE_API_KEY:o?.googleApiKey||"",GOOGLE_GENERATIVE_AI_API_KEY:o?.googleApiKey||"",OLLAMA_HOST:o?.ollamaBaseUrl||"",OLLAMA_CONTEXT_WINDOW:o?.ollamaContextWindow?String(o.ollamaContextWindow):"",VLLM_BASE_URL:o?.vllmBaseUrl||"",BLOCKER_MAX_AUTO_RETRIES:String(r.blockerMaxAutoRetries??3),BLOCKER_AUTO_RETRY_ENABLED:r.blockerAutoRetryEnabled!==!1?"true":"false",PUSH_AFTER_COMMIT:r.pushAfterCommit!==!1?"true":"false",GRACEFUL_SHUTDOWN_ENABLED:r.gracefulShutdownEnabled!==!1?"true":"false",MAX_PARALLEL_EXPERTS:String(r.maxParallelExperts??4),REVIEW_ENABLED:e.skipManagerReview===!1?"true":"false",SELF_REVIEW_ENABLED:_o(e)||r.selfReviewEnabled!==!1?"true":"false"};for(let[g,T]of Object.entries(j))T!==""&&a.push("-e",`${g}=${T}`);let F=t.workerImage,I=$t(F);I&&ht(I),a.push(F);let L=e.skipManagerReview===!1;console.log(`${x()} ${n} ${u.dim("Starting container")} ${u.yellow(s)}`),console.log(`${x()} ${n} ${u.dim(` skipManagerReview=${e.skipManagerReview} \u2192 REVIEW_ENABLED=${L}`)}`),console.log(`${x()} ${n} ${u.dim(` model=${e.workerModel} repo=${e.githubRepo}`)}`),console.log(`${x()} ${n} ${u.dim(` totalRamGB=${l} docker args:`)} ${a.slice(0,10).join(" ")}`);let $=dt("docker",a,{stdio:["ignore","pipe","pipe"],detached:!1});if(!$.pid){console.error(`${x()} ${n} ${u.red("\u2717")} Failed to spawn container`);return}let D={taskId:e.id,containerName:s,process:$,startedAt:new Date,status:"running",resultEmitted:!1};V.set(e.id,D),$.stdout?.on("data",g=>{let T=g.toString().split(`
20
+ `).filter(b=>b.trim());for(let b of T)console.log(`${x()} ${n} ${u.dim(b)}`),b.includes("::result::")&&(D.resultEmitted=!0)}),$.stderr?.on("data",g=>{let T=g.toString().split(`
21
+ `).filter(b=>b.trim());for(let b of T)console.log(`${x()} ${n} ${u.red(b)}`)}),$.on("exit",g=>{D.status=g===0?"completed":"failed";let T=Math.round((Date.now()-D.startedAt.getTime())/1e3),b=g===0?u.green("\u2713"):u.red("\u2717"),Q=g===0?u.green("completed"):u.red(`failed (exit ${g})`);console.log(`${x()} ${n} ${b} Container ${Q} ${u.dim(`(${T}s)`)}`),D.resultEmitted||(console.log(`${x()} ${n} ${u.yellow("\u26A0")} No ::result:: marker seen \u2014 posting fallback completion in 15s`),setTimeout(async()=>{try{let K=g===0?"completed":"failed",Y=g!==0?`Worker container exited with code ${g} without reporting completion`:void 0;(await(await fetch(`${t.apiUrl}/api/tasks/${e.id}/worker-complete`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":t.apiKey},body:JSON.stringify({exitCode:g??1,result:K,errorMessage:Y})})).json()).status==="ignored"?console.log(`${x()} ${n} ${u.dim("Fallback completion ignored (task already transitioned)")}`):console.log(`${x()} ${n} ${u.yellow("\u26A0")} Fallback completion applied: ${K}`)}catch(K){console.error(`${x()} ${n} ${u.red("\u2717")} Fallback completion failed:`,K instanceof Error?K.message:K)}},15e3)),setTimeout(()=>V.delete(e.id),9e4)}),$.on("error",g=>{D.status="failed",console.error(`${x()} ${n} ${u.red("\u2717")} Container error: ${g.message}`)})}function Fe(){return Array.from(V.values()).filter(e=>e.status==="running").length}function It(){return Array.from(V.values()).filter(e=>e.status==="running").map(e=>e.taskId)}function Et(e){let t=V.get(e);if(!t||t.status!=="running")return;let r=u.cyan(e.slice(0,8));console.log(`${x()} ${r} ${u.red("\u25A0")} Stopping container (cancelled by dashboard)`);try{_e(`docker stop ${t.containerName}`,{stdio:"ignore",timeout:15e3}),t.status="completed"}catch{}V.delete(e)}async function At(){console.log(`${x()} ${u.dim(`Stopping ${V.size} containers...`)}`);for(let[,e]of V)if(e.status==="running")try{_e(`docker stop ${e.containerName}`,{stdio:"ignore",timeout:15e3}),e.status="completed"}catch{}V.clear()}async function Tt(e,t,r){let o=u.cyan(e.id.slice(0,8)),n=`manager-${e.id}`;if(V.has(n))return;let s=`wm-manager-${e.id.slice(0,8)}-${Date.now()}`,i=ft(),a=["run","--rm","--name",s,"--memory=4g","--cpus=2"];if(pt){let I=gt();a.push(`--add-host=host.docker.internal:${I||"host-gateway"}`)}else a.push("--network","host");if(i){let I=mt(i);a.push("-v",`${I}:/home/worker/.claude`)}let l=t.apiUrl.replace(/localhost|127\.0\.0\.1/,"host.docker.internal"),E=e.scmProvider||"github",A=yt(E,t,r),d=r?.githubToken||t.githubToken,w=E==="bitbucket"&&r?.scmToken||t.bitbucketToken,P=E==="gitlab"&&r?.scmToken||t.gitlabToken,M={NODE_OPTIONS:"--max-old-space-size=3072",TASK_ID:e.id,MANAGER_ACTION:e.managerAction,JIRA_ISSUE_KEY:e.jiraIssueKey||"",JIRA_SUMMARY:e.summary||"",JIRA_DESCRIPTION:e.description||"",GITHUB_REPO:e.githubRepo||"",PR_URL:e.githubPrUrl||"",PR_NUMBER:e.githubPrNumber?String(e.githubPrNumber):"",API_BASE_URL:l,ORG_API_KEY:t.apiKey,SCM_PROVIDER:E,SCM_TOKEN:A,GITHUB_TOKEN:d,GH_TOKEN:d,BITBUCKET_TOKEN:w,BITBUCKET_USERNAME:r?.bitbucketUsername||"x-token-auth",GITLAB_TOKEN:P,MANAGER_PROVIDER:r?.managerProvider||"anthropic",MANAGER_MODEL:r?.managerModelId||"",ANTHROPIC_API_KEY:i?"":r?.anthropicApiKey||process.env.ANTHROPIC_API_KEY||"",OPENAI_API_KEY:r?.openaiApiKey||"",GOOGLE_API_KEY:r?.googleApiKey||"",JIRA_BASE_URL:r?.jiraBaseUrl||"",JIRA_EMAIL:r?.jiraEmail||"",JIRA_API_TOKEN:r?.jiraApiToken||"",TICKET_SYSTEM:r?.issueTrackerProvider||"jira",LINEAR_API_KEY:r?.linearApiKey||""};for(let[I,L]of Object.entries(M))L!==""&&a.push("-e",`${I}=${L}`);let R=t.workerImage,O=$t(R);O&&ht(O),a.push("--entrypoint","/bin/bash"),a.push(R),a.push("/app/manager-entrypoint.sh"),console.log(`${x()} ${o} ${u.magenta("\u25C6 MANAGER")} Starting ${e.managerAction} container ${u.yellow(s)}`);let j=dt("docker",a,{stdio:["ignore","pipe","pipe"],detached:!1});if(!j.pid){console.error(`${x()} ${o} ${u.red("\u2717")} Failed to spawn manager container`);return}let F={taskId:n,containerName:s,process:j,startedAt:new Date,status:"running",resultEmitted:!1};V.set(n,F),j.stdout?.on("data",I=>{let L=I.toString().split(`
22
+ `).filter($=>$.trim());for(let $ of L)console.log(`${x()} ${o} ${u.magenta("MGR")} ${u.dim($)}`)}),j.stderr?.on("data",I=>{let L=I.toString().split(`
23
+ `).filter($=>$.trim());for(let $ of L)console.log(`${x()} ${o} ${u.magenta("MGR")} ${u.red($)}`)}),j.on("exit",I=>{F.status=I===0?"completed":"failed";let L=Math.round((Date.now()-F.startedAt.getTime())/1e3),$=I===0?u.green("\u2713"):u.red("\u2717"),D=I===0?u.green("completed"):u.red(`failed (exit ${I})`);console.log(`${x()} ${o} ${u.magenta("MGR")} ${$} Manager ${e.managerAction} ${D} ${u.dim(`(${L}s)`)}`),setTimeout(()=>V.delete(n),6e4)}),j.on("error",I=>{F.status="failed",console.error(`${x()} ${o} ${u.magenta("MGR")} ${u.red("\u2717")} Container error: ${I.message}`)})}import{createRequire as Ro}from"module";var So=Ro(import.meta.url),ko=So("../package.json"),se=ko.version;import{spawn as bt}from"child_process";import he from"chalk";async function ke(){return console.log(he.cyan(" Updating @workermill/agent...")),new Promise(e=>{let t=bt("npm",["install","-g","@workermill/agent@latest"],{stdio:"inherit",shell:!0});t.on("error",r=>{console.error(he.red(` Update failed: ${r.message}`)),e(!1)}),t.on("close",r=>{r===0?(console.log(he.green(" Update successful.")),e(!0)):(console.error(he.red(` Update failed with exit code ${r}`)),e(!1))})})}function Pe(){console.log(he.cyan(" Restarting agent...")),bt(process.argv[0],process.argv.slice(1),{stdio:"inherit",detached:!0}).unref(),process.exit(0)}var le=new Set,ce=new Set,ve=null,_t=!1,Ce=!1;function N(){return y.dim(new Date().toLocaleTimeString())}async function Po(){if(ve)return ve;try{return ve=(await C.get("/api/agent/config")).data,ve}catch{return console.error(`${N()} ${y.red("\u2717")} Failed to fetch org config`),{}}}async function Rt(e){if(!Ce)try{let t=await C.get("/api/agent/poll",{params:{agentId:e.agentId}}),r=t.data.tasks;if(r.length===0)return;for(let n of r)n.status==="planning"&&!le.has(n.id)?await vo(n,e):n.status==="queued"&&await Co(n,e);let o=t.data.managerTasks;if(o&&o.length>0)for(let n of o)ce.has(n.id)||await xo(n,e)}catch(t){let r=t,o=le.size>0||Fe()>0||ce.size>0;r.response?.status===401?o||console.error(`${N()} ${y.red("\u2717")} Authentication failed. Check your API key.`):o||console.warn(`${N()} ${y.yellow("\u26A0")} Poll error: ${r.message||String(t)}`)}}async function vo(e,t){let r,o;try{let i=await C.post("/api/agent/claim",{taskId:e.id,agentId:t.agentId});if(!i.data.claimed)return;r=i.data.credentials,o=i.data.task}catch{return}let n=y.cyan(e.id.slice(0,8));if(o&&(o.retryCount??0)>0&&o.executionPlanV2!=null){console.log(),console.log(`${N()} ${y.magenta("\u25C6 RESUME")} ${n} ${e.summary.substring(0,60)}`),console.log(`${N()} ${n} Retry #${o.retryCount} with existing plan \u2014 skipping planning`),le.add(e.id);try{await C.post("/api/agent/resume-plan",{taskId:e.id,agentId:t.agentId}),console.log(`${N()} ${n} ${y.green("\u2713")} Resumed with existing plan \u2192 ${y.green("queued")}`)}catch(i){let a=i,l=a.response?.data?.error||a.message||String(i);console.error(`${N()} ${n} ${y.red("\u2717")} Resume failed: ${l}`)}le.delete(e.id);return}console.log(),console.log(`${N()} ${y.magenta("\u25C6 PLANNING")} ${n} ${e.summary.substring(0,60)}`),le.add(e.id),ct(e,t,r).then(i=>{console.log(i?`${N()} ${y.green("\u2713")} Planning complete for ${n}`:`${N()} ${y.red("\u2717")} Planning failed for ${n}`)}).catch(i=>console.error(`${N()} ${y.red("\u2717")} Planning error for ${n}:`,i.message||i)).finally(()=>le.delete(e.id))}async function Co(e,t){if(Fe()>=t.maxWorkers)return;let o;try{if(o=(await C.post("/api/agent/claim",{taskId:e.id,agentId:t.agentId})).data,!o.claimed)return}catch{return}try{await C.post("/api/agent/started",{taskId:e.id,agentId:t.agentId})}catch{let E=y.cyan(e.id.slice(0,8));console.warn(`${N()} ${y.yellow("\u26A0")} Failed to report started for ${E}`)}let n=y.cyan(e.id.slice(0,8));console.log(),console.log(`${N()} ${y.blue("\u25B6 EXECUTING")} ${n} ${e.summary.substring(0,60)}`);let s=await Po(),i=o.task||{id:e.id,summary:e.summary,description:e.description,jiraIssueKey:e.jiraIssueKey,workerModel:e.workerModel,workerProvider:e.workerProvider,githubRepo:e.githubRepo,scmProvider:e.scmProvider,skipManagerReview:e.skipManagerReview,deploymentEnabled:e.deploymentEnabled,improvementEnabled:e.improvementEnabled,qualityGateBypass:e.qualityGateBypass,standardSdkMode:e.standardSdkMode,parentTaskId:e.parentTaskId,taskNotes:e.taskNotes,githubPrUrl:e.githubPrUrl,githubPrNumber:e.githubPrNumber,executionPlanV2:e.executionPlanV2,jiraFields:e.jiraFields||{}},a=o.credentials;wt(i,t,s,a).catch(l=>console.error(`${N()} ${y.red("\u2717")} Spawn failed for ${n}:`,l.message||l))}async function xo(e,t){let r=y.cyan(e.id.slice(0,8));ce.add(e.id);try{let o=await C.post("/api/agent/claim-manager",{taskId:e.id,agentId:t.agentId,action:e.managerAction});if(!o.data.claimed){ce.delete(e.id);return}let n=o.data.task,s=o.data.credentials||{},i=e.managerAction==="analyze_logs"?y.yellow("LOG ANALYSIS"):y.yellow("PR REVIEW");console.log(),console.log(`${N()} ${y.magenta("\u25C6 MANAGER")} ${i} ${r} ${e.summary.substring(0,60)}`);let a={id:e.id,summary:n?.summary||e.summary,description:n?.description||e.description,jiraIssueKey:n?.jiraIssueKey||e.jiraIssueKey,githubRepo:n?.githubRepo||e.githubRepo,scmProvider:n?.scmProvider||e.scmProvider,githubPrUrl:n?.githubPrUrl||e.githubPrUrl,githubPrNumber:n?.githubPrNumber||e.githubPrNumber,managerAction:e.managerAction};Tt(a,t,s).then(()=>{console.log(`${N()} ${r} ${y.magenta("MGR")} ${y.green("\u2713")} Manager ${e.managerAction} dispatched`)}).catch(l=>{console.error(`${N()} ${r} ${y.magenta("MGR")} ${y.red("\u2717")} Manager spawn failed:`,l.message||l)}).finally(()=>ce.delete(e.id))}catch(o){ce.delete(e.id);let n=o;console.error(`${N()} ${r} ${y.red("\u2717")} Failed to claim manager task:`,n.message||String(o))}}var xe=null,Me=null;function St(){xe&&(clearInterval(xe),xe=null),Me&&(clearInterval(Me),Me=null)}function kt(e){console.log(` ${y.dim("Polling every")} ${e.pollIntervalMs/1e3}s ${y.dim("\xB7 waiting for tasks...")}`),Rt(e),xe=setInterval(()=>Rt(e),e.pollIntervalMs)}function Pt(e){Me=setInterval(async()=>{let t=It(),r=Array.from(le),o=Array.from(ce),n=[...t,...r,...o];try{let s=await C.post("/api/agent/heartbeat",{agentId:e.agentId,activeTasks:n,agentVersion:se}),i=s.data?.cancelledTasks;if(i&&i.length>0)for(let A of i)Et(A);let{updateAvailable:a,updateRequired:l,latestVersion:E}=s.data??{};l&&!Ce?(Ce=!0,console.log(`${N()} ${y.red("\u26A0 Agent update required")} (current: ${se}, required: ${E})`),console.log(`${N()} ${y.yellow("Refusing new tasks until updated.")}`),await ke()?Pe():(console.log(`${N()} ${y.red("Auto-update failed.")} Run: npm install -g @workermill/agent@latest`),Ce=!1)):a&&!_t&&!l&&(_t=!0,console.log(`${N()} ${y.yellow(`Update available: ${E}`)} (current: ${se}). Run: workermill-agent update`))}catch{}},e.heartbeatIntervalMs)}we();async function Mo(e){console.log(),console.log(U.bold.cyan(" WorkerMill Remote Agent")),console.log(U.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(),Ke(e.apiUrl,e.apiKey);try{let t=await C.get("/api/agent/config"),r=t.data.maxConcurrentWorkers;r&&typeof r=="number"&&(e.maxWorkers=r),t.data.workerImageUrl&&(e.workerImage=t.data.workerImageUrl),console.log(` ${U.green("\u25CF")} Connected to ${U.cyan(e.apiUrl)}`),console.log(` ${U.dim("Agent:")} ${e.agentId}`),console.log(` ${U.dim("Workers:")} ${U.yellow(String(e.maxWorkers))} parallel`),console.log(` ${U.dim("Image:")} ${e.workerImage}`),console.log(` ${U.dim("SCM:")} ${t.data.scmProvider}`),console.log(` ${U.dim("Model:")} ${U.yellow(t.data.defaultWorkerModel)}`),console.log()}catch(t){let r=t;throw r.response?.status===401?new Error("Authentication failed. Check your API key."):new Error(`Failed to connect to WorkerMill API: ${r.message||String(t)}`)}try{let t=await C.post("/api/agent/register",{agentId:e.agentId,maxWorkers:e.maxWorkers,agentVersion:se}),{updateAvailable:r,updateRequired:o,latestVersion:n}=t.data;o?(console.log(U.red(` \u26A0 Agent update required (current: ${se}, required: ${n})`)),await ke()?Pe():console.log(U.yellow(" Auto-update failed. Run: npm install -g @workermill/agent@latest"))):r&&console.log(U.yellow(` Update available: ${n} (current: ${se}). Run: workermill-agent update`))}catch{}return kt(e),Pt(e),console.log(U.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(` ${U.green("\u25CF")} Agent is running. ${U.dim("Press Ctrl+C to stop.")}`),console.log(),async()=>{console.log(),console.log(U.dim(" Shutting down...")),St();try{await C.post("/api/agent/deregister",{agentId:e.agentId})}catch{}await At(),console.log(` ${U.red("\u25CF")} Agent stopped.`)}}var Oo=typeof process<"u"&&process.argv[1]&&(process.argv[1].endsWith("/index.ts")||process.argv[1].endsWith("/index.js"));if(Oo){try{await import("dotenv/config")}catch{}let{loadConfig:e,validatePrerequisites:t}=await Promise.resolve().then(()=>(we(),Ye)),r=e();t(),console.log(U.dim(" Prerequisites validated."));let o=await Mo(r);process.on("SIGINT",async()=>{await o(),process.exit(0)}),process.on("SIGTERM",async()=>{await o(),process.exit(0)})}export{ie as findClaudePath,Ve as getSystemInfo,Be as loadConfig,Ge as loadConfigFromFile,Mo as startAgent,We as validatePrerequisites};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workermill/agent",
3
- "version": "0.8.5",
3
+ "version": "0.8.9",
4
4
  "description": "WorkerMill Remote Agent - Run AI workers locally with your Claude Max subscription",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -12,7 +12,7 @@
12
12
  "README.md"
13
13
  ],
14
14
  "scripts": {
15
- "build": "tsc",
15
+ "build": "tsc; node build.mjs",
16
16
  "dev": "tsc --watch",
17
17
  "typecheck": "tsc --noEmit"
18
18
  },
@@ -34,6 +34,7 @@
34
34
  "devDependencies": {
35
35
  "@types/inquirer": "^9.0.9",
36
36
  "@types/node": "^22.0.0",
37
+ "esbuild": "^0.27.3",
37
38
  "typescript": "^5.5.0"
38
39
  }
39
40
  }