@workermill/agent 0.8.3 → 0.8.8

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.
@@ -311,29 +311,9 @@ export async function setupCommand() {
311
311
  }
312
312
  process.exit(1);
313
313
  }
314
- // SCM token
315
- const tokenPrompts = {
316
- github: "",
317
- bitbucket: "",
318
- gitlab: "",
319
- };
320
- const scmLabel = scmProvider === "bitbucket"
321
- ? "Bitbucket"
322
- : scmProvider === "gitlab"
323
- ? "GitLab"
324
- : "GitHub";
325
- const { scmToken } = await inquirer.prompt([
326
- {
327
- type: "password",
328
- name: "scmToken",
329
- message: `${scmLabel} personal access token (for cloning/pushing to your repos):`,
330
- mask: "*",
331
- validate: (v) => (v.length > 0 ? true : "A token is required for workers to clone and push to your repositories"),
332
- },
333
- ]);
334
- if (scmToken) {
335
- tokenPrompts[scmProvider] = scmToken;
336
- }
314
+ // SCM tokens come from org Settings > Integrations (no local prompt needed)
315
+ console.log(chalk.dim(" SCM tokens are managed via Settings > Integrations on the dashboard."));
316
+ console.log();
337
317
  const { agentId } = await inquirer.prompt([
338
318
  {
339
319
  type: "input",
@@ -342,36 +322,72 @@ export async function setupCommand() {
342
322
  default: `agent-${hostname()}`,
343
323
  },
344
324
  ]);
345
- // ── Step 7: Pull worker image ─────────────────────────────────────────────
325
+ // ── Step 7: AWS CLI + ECR auth + pull worker image ───────────────────────
346
326
  console.log();
347
- const workerImage = "public.ecr.aws/a7k5r0v0/workermill-worker:latest";
348
- console.log(chalk.dim(` Pulling worker image: ${workerImage}`));
349
- console.log(chalk.dim(" This may take a few minutes on first run (~1.1 GB)..."));
350
- console.log();
351
- // Use spawnSync to show progress AND capture errors
352
- const pullResult = spawnSync("docker", ["pull", workerImage], {
353
- stdio: "inherit",
354
- timeout: 600_000, // 10 minutes
355
- });
356
- if (pullResult.status === 0) {
357
- console.log();
358
- console.log(chalk.green(` ✓ Worker image pulled`));
327
+ const PRIVATE_ECR_REGISTRY = "593971626975.dkr.ecr.us-east-1.amazonaws.com";
328
+ const workerImage = `${PRIVATE_ECR_REGISTRY}/workermill-dev/worker:latest`;
329
+ // Check AWS CLI
330
+ const awsSpinner = ora("Checking AWS CLI...").start();
331
+ if (commandExists("aws")) {
332
+ try {
333
+ execSync("aws sts get-caller-identity", {
334
+ stdio: "pipe",
335
+ timeout: 15000,
336
+ });
337
+ awsSpinner.succeed("AWS CLI configured");
338
+ }
339
+ catch {
340
+ awsSpinner.warn("AWS CLI found but credentials not configured");
341
+ console.log();
342
+ console.log(chalk.yellow(" Configure AWS credentials for private ECR image access:"));
343
+ console.log(chalk.cyan(" aws configure"));
344
+ console.log(chalk.dim(" Contact your WorkerMill admin for AWS access key / secret key."));
345
+ console.log(chalk.dim(" Setup will continue — you can configure AWS later before starting."));
346
+ }
359
347
  }
360
348
  else {
349
+ awsSpinner.warn("AWS CLI not found");
361
350
  console.log();
362
- console.log(chalk.red(" Failed to pull worker image."));
351
+ console.log(chalk.yellow(" AWS CLI is required for pulling worker images from private ECR."));
352
+ console.log(chalk.cyan(" Install: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"));
353
+ console.log(chalk.dim(" Setup will continue — install AWS CLI before starting the agent."));
354
+ }
355
+ // Authenticate with ECR
356
+ console.log();
357
+ console.log(chalk.dim(` Authenticating with private ECR...`));
358
+ let ecrAuthed = false;
359
+ try {
360
+ execSync(`aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${PRIVATE_ECR_REGISTRY}`, { stdio: "pipe", timeout: 30_000 });
361
+ console.log(chalk.green(" ✓ ECR authenticated"));
362
+ ecrAuthed = true;
363
+ }
364
+ catch {
365
+ console.log(chalk.yellow(" ⚠ ECR authentication failed (AWS credentials may not be configured yet)"));
366
+ console.log(chalk.dim(" Worker image pull will be skipped — authenticate later with:"));
367
+ console.log(chalk.dim(` aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${PRIVATE_ECR_REGISTRY}`));
368
+ }
369
+ // Pull worker image (only if ECR auth succeeded)
370
+ if (ecrAuthed) {
363
371
  console.log();
364
- if (pullResult.error) {
365
- console.log(chalk.yellow(` Error: ${pullResult.error.message}`));
372
+ console.log(chalk.dim(` Pulling worker image: ${workerImage}`));
373
+ console.log(chalk.dim(" This may take a few minutes on first run (~1.1 GB)..."));
374
+ console.log();
375
+ const pullResult = spawnSync("docker", ["pull", workerImage], {
376
+ stdio: "inherit",
377
+ timeout: 600_000,
378
+ });
379
+ if (pullResult.status === 0) {
380
+ console.log();
381
+ console.log(chalk.green(" ✓ Worker image pulled"));
366
382
  }
367
- console.log(chalk.yellow(" Troubleshooting:"));
368
- console.log(chalk.yellow(" 1. Is Docker Desktop running?"));
369
- if (isWindows) {
370
- console.log(chalk.yellow(" 2. Is Docker in Linux containers mode? (Right-click Docker tray icon)"));
383
+ else {
384
+ console.log();
385
+ console.log(chalk.red(" ✗ Failed to pull worker image."));
386
+ if (pullResult.error) {
387
+ console.log(chalk.yellow(` Error: ${pullResult.error.message}`));
388
+ }
389
+ console.log(chalk.dim(" Setup will continue — you can pull the image later before starting."));
371
390
  }
372
- console.log(chalk.yellow(` ${isWindows ? "3" : "2"}. Try manually: ${chalk.cyan(`docker pull ${workerImage}`)}`));
373
- console.log();
374
- console.log(chalk.dim(" Setup will continue — you can pull the image later before starting."));
375
391
  }
376
392
  // ── Step 8: Save config ───────────────────────────────────────────────────
377
393
  const fileConfig = {
@@ -381,7 +397,7 @@ export async function setupCommand() {
381
397
  maxWorkers: 1,
382
398
  pollIntervalMs: 5000,
383
399
  heartbeatIntervalMs: 30000,
384
- tokens: tokenPrompts,
400
+ tokens: { github: "", bitbucket: "", gitlab: "" }, // SCM tokens come from org Settings
385
401
  workerImage,
386
402
  setupCompletedAt: new Date().toISOString(),
387
403
  };
package/dist/config.js CHANGED
@@ -53,10 +53,12 @@ export function loadConfigFromFile() {
53
53
  console.error("Config file is missing required fields (apiUrl, apiKey). Re-run 'workermill-agent setup'.");
54
54
  process.exit(1);
55
55
  }
56
- // Migrate any stale image URLs to current default
57
- const defaultImage = "public.ecr.aws/a7k5r0v0/workermill-worker:latest";
56
+ // Migrate any stale image URLs to current default (private ECR)
57
+ const defaultImage = "593971626975.dkr.ecr.us-east-1.amazonaws.com/workermill-dev/worker:latest";
58
58
  let workerImage = fc.workerImage || defaultImage;
59
- if (workerImage.includes("jarod1/") || !workerImage.includes("workermill-worker")) {
59
+ if (workerImage.includes("jarod1/") ||
60
+ workerImage.includes("public.ecr.aws/") ||
61
+ !workerImage.includes("workermill-worker") && !workerImage.includes("workermill-dev/worker")) {
60
62
  workerImage = defaultImage;
61
63
  fc.workerImage = workerImage;
62
64
  try {
@@ -151,7 +153,7 @@ export function findClaudePath() {
151
153
  */
152
154
  export function checkPrerequisites(workerImage) {
153
155
  const results = [];
154
- const image = workerImage || "public.ecr.aws/a7k5r0v0/workermill-worker:latest";
156
+ const image = workerImage || "593971626975.dkr.ecr.us-east-1.amazonaws.com/workermill-dev/worker:latest";
155
157
  // Docker
156
158
  try {
157
159
  const version = execSync("docker version --format {{.Server.Version}}", {
@@ -18,7 +18,7 @@ export interface PlannedStory {
18
18
  priority: number;
19
19
  estimatedEffort: "small" | "medium" | "large";
20
20
  dependencies: string[];
21
- acceptanceCriteria: string[];
21
+ acceptanceCriteria?: string[];
22
22
  targetFiles?: string[];
23
23
  scope?: string;
24
24
  }
@@ -39,7 +39,7 @@ export interface CriticResult {
39
39
  suggestedChanges?: string[];
40
40
  }>;
41
41
  }
42
- declare const AUTO_APPROVAL_THRESHOLD = 80;
42
+ declare const AUTO_APPROVAL_THRESHOLD = 85;
43
43
  /**
44
44
  * Parse execution plan JSON from raw Claude CLI output.
45
45
  * Mirrors server-side parseExecutionPlan() in planning-agent-local.ts.
@@ -17,7 +17,7 @@ import { api } from "./api.js";
17
17
  // CONSTANTS
18
18
  // ============================================================================
19
19
  const MAX_TARGET_FILES = 15;
20
- const AUTO_APPROVAL_THRESHOLD = 80;
20
+ const AUTO_APPROVAL_THRESHOLD = 85;
21
21
  // ============================================================================
22
22
  // PLAN PARSING
23
23
  // ============================================================================
@@ -217,12 +217,13 @@ Review this execution plan against the PRD:
217
217
 
218
218
  **DO check for:**
219
219
  1. **Missing Requirements** - Does the plan cover what the PRD asks for?
220
- 2. **Vague Instructions** - Will the worker know what to do?
220
+ 2. **Scope Clarity** - Is each story's description a brief file scope label (1 line)? Stories should NOT rewrite ticket requirements.
221
221
  3. **Security Issues** - Only for tasks involving auth, user data, or external input
222
- 4. **Unrealistic Scope** - Any step targeting >5 files MUST score below 80 (auto-rejection threshold). Each step should modify at most 5 files. If a step needs more, split it into multiple steps first.
222
+ 4. **Unrealistic Scope** - Any step targeting >5 files MUST score below 85 (auto-rejection threshold). Each step should modify at most 5 files. If a step needs more, split it into multiple steps first.
223
223
  5. **Missing Operational Steps** - If the PRD requires deployment, provisioning, migrations, or running commands, does the plan include operational steps? Writing code is not the same as deploying it.
224
224
  6. **Overlapping File Scope** - If two or more steps share the same targetFiles, this causes parallel merge conflicts. Steps MUST NOT overlap on targetFiles. Deduct 10 points per shared file across steps.
225
225
  7. **Serialization Bottleneck** - If more than half the stories depend on a single story that targets >5 files, the plan has a bottleneck. Deduct 15 points — split the foundation or allow more parallel work.
226
+ 8. **Requirement Rewriting** - If any story description contains implementation details, acceptance criteria, or rewritten requirements from the PRD, deduct 15 points per offending story. Story descriptions must be ONE-LINE file scope labels (e.g., "Database layer — migrations and entity definitions"). The original ticket is the spec.
226
227
 
227
228
  ## Scoring Guide
228
229
 
@@ -237,7 +238,7 @@ Respond with ONLY a JSON object (no markdown, no explanation):
237
238
  {"approved": boolean, "score": number, "risks": ["risk1", "risk2"], "suggestions": ["suggestion1", "suggestion2"], "storyFeedback": [{"storyId": "step-0", "feedback": "specific feedback", "suggestedChanges": ["change1"]}]}
238
239
 
239
240
  Rules:
240
- - approved = true if score >= 80 AND plan is right-sized for task
241
+ - approved = true if score >= 85 AND plan is right-sized for task
241
242
  - risks = specific issues (empty array if none)
242
243
  - suggestions = actionable improvements (empty array if none)
243
244
  - storyFeedback = per-step feedback (optional, only for steps that need changes)`;
package/dist/planner.js CHANGED
@@ -625,9 +625,9 @@ export async function planTask(task, config, credentials) {
625
625
  // 2e. Check critic result
626
626
  if (!criticResult) {
627
627
  // Critic failed (timeout, parse error, etc.) — post plan without critic gate
628
- const msg = `${PREFIX} Critic validation failed posting plan without critic score`;
628
+ const msg = `${PREFIX} ⚠️ CRITIC BYPASSED — Critic validation failed (timeout/parse error). Posting plan WITHOUT quality gate.`;
629
629
  console.log(`${ts()} ${taskLabel} ${chalk.yellow("⚠")} ${msg}`);
630
- await postLog(task.id, msg);
630
+ await postLog(task.id, msg, "error", "warning");
631
631
  const planningDurationMs = Date.now() - startTime;
632
632
  return await postValidatedPlan(task.id, plan, config.agentId, taskLabel, elapsed, undefined, undefined, criticHistory, totalFileCapTruncations, planningDurationMs, iteration);
633
633
  }
package/dist/spawner.d.ts CHANGED
@@ -51,6 +51,9 @@ export interface ClaimCredentials {
51
51
  githubReviewerToken?: string;
52
52
  scmBaseUrl?: string;
53
53
  ollamaContextWindow?: number;
54
+ scmToken?: string;
55
+ githubToken?: string;
56
+ bitbucketUsername?: string;
54
57
  anthropicApiKey?: string;
55
58
  openaiApiKey?: string;
56
59
  googleApiKey?: string;
@@ -102,6 +105,9 @@ export interface ManagerCredentials {
102
105
  jiraApiToken?: string;
103
106
  linearApiKey?: string;
104
107
  issueTrackerProvider?: string;
108
+ scmToken?: string;
109
+ githubToken?: string;
110
+ bitbucketUsername?: string;
105
111
  }
106
112
  /**
107
113
  * Spawn a manager Docker container for PR review or log analysis.
package/dist/spawner.js CHANGED
@@ -34,6 +34,21 @@ function detectWSL() {
34
34
  }
35
35
  const isWSL = detectWSL();
36
36
  const isDockerDesktop = isWSL || process.platform === "darwin" || process.platform === "win32";
37
+ /**
38
+ * Get WSL2 host IP for Docker --add-host override.
39
+ * On WSL2, host-gateway resolves to the Docker VM, not WSL2 where the API runs.
40
+ */
41
+ function getWSLHostIP() {
42
+ if (!isWSL)
43
+ return null;
44
+ try {
45
+ const ip = execSync("hostname -I", { encoding: "utf-8" }).trim().split(/\s+/)[0];
46
+ if (ip && /^\d+\.\d+\.\d+\.\d+$/.test(ip))
47
+ return ip;
48
+ }
49
+ catch { /* fall through */ }
50
+ return null;
51
+ }
37
52
  /**
38
53
  * Convert WSL paths to Windows paths for Docker volume mounts.
39
54
  */
@@ -69,14 +84,42 @@ function findClaudeConfigDir() {
69
84
  }
70
85
  return null;
71
86
  }
87
+ /** Private ECR registry for worker images */
88
+ const PRIVATE_ECR_REGISTRY = "593971626975.dkr.ecr.us-east-1.amazonaws.com";
89
+ /** Cache ECR login (token lasts 12h, refresh after 11h) */
90
+ let ecrLoginExpiresAt = 0;
91
+ /**
92
+ * Ensure Docker is logged into private ECR.
93
+ * Uses ambient AWS credentials (aws configure).
94
+ */
95
+ function ensureEcrLogin() {
96
+ if (Date.now() < ecrLoginExpiresAt)
97
+ return true;
98
+ try {
99
+ execSync(`aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${PRIVATE_ECR_REGISTRY}`, { stdio: "pipe", timeout: 30_000 });
100
+ ecrLoginExpiresAt = Date.now() + 11 * 60 * 60 * 1000;
101
+ return true;
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ }
72
107
  /**
73
108
  * Get SCM token based on provider.
109
+ * Prefers org credentials from API, falls back to local config.
74
110
  */
75
- function getScmToken(scmProvider, config) {
111
+ function getScmToken(scmProvider, config, credentials) {
112
+ // Org credentials from API are the primary source
113
+ if (credentials?.scmToken)
114
+ return credentials.scmToken;
115
+ // Fall back to local config tokens
76
116
  switch (scmProvider) {
77
- case "bitbucket": return config.bitbucketToken;
78
- case "gitlab": return config.gitlabToken;
79
- default: return config.githubToken;
117
+ case "bitbucket":
118
+ return config.bitbucketToken;
119
+ case "gitlab":
120
+ return config.gitlabToken;
121
+ default:
122
+ return config.githubToken;
80
123
  }
81
124
  }
82
125
  /** Check if a task has the self-review label (works across Jira, GitHub, GitLab, Linear) */
@@ -112,7 +155,9 @@ export async function spawnWorker(task, config, orgConfig, credentials) {
112
155
  }
113
156
  const containerName = `workermill-${task.id.slice(0, 8)}`;
114
157
  // Build Docker run arguments
115
- const dockerArgs = ["run", "--rm", "--pull", "always", "--name", containerName];
158
+ // Only pull from registry for remote images; local images (no '/' in name) are already on disk
159
+ const isLocalImage = !config.workerImage.includes("/");
160
+ const dockerArgs = ["run", "--rm", ...(isLocalImage ? [] : ["--pull", "always"]), "--name", containerName];
116
161
  // Resource limits — 6GB memory with swap for overflow.
117
162
  // NODE_OPTIONS caps V8 heap at 2GB; the extra room is for git, npm, Claude CLI subprocesses.
118
163
  const totalRamGB = Math.round(os.totalmem() / (1024 * 1024 * 1024));
@@ -125,9 +170,10 @@ export async function spawnWorker(task, config, orgConfig, credentials) {
125
170
  else {
126
171
  dockerArgs.push("--memory", "6g", "--memory-swap", "12g", "--cpus", "4");
127
172
  }
128
- // Network mode
173
+ // Network mode — WSL2: host-gateway resolves to Docker VM, not WSL2, so use actual WSL IP
129
174
  if (isDockerDesktop) {
130
- dockerArgs.push("--add-host=host.docker.internal:host-gateway");
175
+ const wslIP = getWSLHostIP();
176
+ dockerArgs.push(`--add-host=host.docker.internal:${wslIP || "host-gateway"}`);
131
177
  }
132
178
  else {
133
179
  dockerArgs.push("--network", "host");
@@ -158,8 +204,18 @@ export async function spawnWorker(task, config, orgConfig, credentials) {
158
204
  console.log(`${ts()} ${taskLabel} ${chalk.dim("Skipping Claude mount (non-Anthropic worker)")}`);
159
205
  }
160
206
  // Build environment variables — KEY DIFFERENCE: API_BASE_URL points to cloud
207
+ // Docker containers can't reach host via "localhost" — translate for Docker networking
208
+ const containerApiUrl = config.apiUrl.replace(/localhost|127\.0\.0\.1/, "host.docker.internal");
161
209
  const scmProvider = (task.scmProvider || "github");
162
- const scmToken = getScmToken(scmProvider, config);
210
+ const scmToken = getScmToken(scmProvider, config, credentials);
211
+ // Derive per-provider tokens: prefer org credentials, fall back to local config
212
+ const githubToken = credentials?.githubToken || config.githubToken;
213
+ const bitbucketToken = scmProvider === "bitbucket"
214
+ ? credentials?.scmToken || config.bitbucketToken
215
+ : config.bitbucketToken;
216
+ const gitlabToken = scmProvider === "gitlab"
217
+ ? credentials?.scmToken || config.gitlabToken
218
+ : config.gitlabToken;
163
219
  const envVars = {
164
220
  // Cap V8 heap to 3GB — forces aggressive GC instead of bloating to fill container.
165
221
  // Each Claude CLI subprocess inherits this, preventing unbounded heap growth.
@@ -178,18 +234,19 @@ export async function spawnWorker(task, config, orgConfig, credentials) {
178
234
  RETRY_NUMBER: String(task.retryCount ?? 0),
179
235
  TICKET_KEY: task.jiraIssueKey || "",
180
236
  // Cloud API — this is what makes remote agent mode work
181
- API_BASE_URL: config.apiUrl,
237
+ // Use containerApiUrl so Docker containers reach the host via host.docker.internal
238
+ API_BASE_URL: containerApiUrl,
182
239
  ORG_API_KEY: config.apiKey,
183
- // SCM configuration
240
+ // SCM configuration — org credentials from API are primary, local config is fallback
184
241
  SCM_PROVIDER: scmProvider,
185
242
  SCM_TOKEN: scmToken,
186
243
  SCM_BASE_URL: credentials?.scmBaseUrl || "",
187
- GITHUB_TOKEN: config.githubToken,
188
- GH_TOKEN: config.githubToken,
244
+ GITHUB_TOKEN: githubToken,
245
+ GH_TOKEN: githubToken,
189
246
  GITHUB_REVIEWER_TOKEN: credentials?.githubReviewerToken || "",
190
- BITBUCKET_TOKEN: config.bitbucketToken,
191
- BITBUCKET_USERNAME: "x-token-auth",
192
- GITLAB_TOKEN: config.gitlabToken,
247
+ BITBUCKET_TOKEN: bitbucketToken,
248
+ BITBUCKET_USERNAME: credentials?.bitbucketUsername || "x-token-auth",
249
+ GITLAB_TOKEN: gitlabToken,
193
250
  // Target repository
194
251
  TARGET_REPO: task.githubRepo || "",
195
252
  GITHUB_REPO: task.githubRepo || "",
@@ -269,8 +326,12 @@ export async function spawnWorker(task, config, orgConfig, credentials) {
269
326
  dockerArgs.push("-e", `${k}=${v}`);
270
327
  }
271
328
  }
272
- // Worker image (configurable: Docker Hub for CLI users, local for bin/remote-agent)
273
- dockerArgs.push(config.workerImage || "public.ecr.aws/a7k5r0v0/workermill-worker:latest");
329
+ // Worker image private ECR requires auth (ensureEcrLogin handles it)
330
+ const workerImage = config.workerImage || `${PRIVATE_ECR_REGISTRY}/workermill-dev/worker:latest`;
331
+ if (workerImage.includes(PRIVATE_ECR_REGISTRY)) {
332
+ ensureEcrLogin();
333
+ }
334
+ dockerArgs.push(workerImage);
274
335
  const reviewEnabled = task.skipManagerReview === false;
275
336
  console.log(`${ts()} ${taskLabel} ${chalk.dim("Starting container")} ${chalk.yellow(containerName)}`);
276
337
  console.log(`${ts()} ${taskLabel} ${chalk.dim(` skipManagerReview=${task.skipManagerReview} → REVIEW_ENABLED=${reviewEnabled}`)}`);
@@ -425,13 +486,29 @@ export async function spawnManagerWorker(task, config, credentials) {
425
486
  "--memory=4g",
426
487
  "--cpus=2",
427
488
  ];
489
+ // Network mode — same as main worker spawn
490
+ if (isDockerDesktop) {
491
+ const wslIP = getWSLHostIP();
492
+ dockerArgs.push(`--add-host=host.docker.internal:${wslIP || "host-gateway"}`);
493
+ }
494
+ else {
495
+ dockerArgs.push("--network", "host");
496
+ }
428
497
  if (claudeConfigDir) {
429
498
  const dockerClaudeDir = toDockerPath(claudeConfigDir);
430
499
  dockerArgs.push("-v", `${dockerClaudeDir}:/home/worker/.claude`);
431
500
  }
432
501
  // Manager-specific env vars (match ECS runManagerTask)
502
+ const containerApiUrl = config.apiUrl.replace(/localhost|127\.0\.0\.1/, "host.docker.internal");
433
503
  const scmProvider = (task.scmProvider || "github");
434
- const scmToken = getScmToken(scmProvider, config);
504
+ const scmToken = getScmToken(scmProvider, config, credentials);
505
+ const githubToken = credentials?.githubToken || config.githubToken;
506
+ const bitbucketToken = scmProvider === "bitbucket"
507
+ ? credentials?.scmToken || config.bitbucketToken
508
+ : config.bitbucketToken;
509
+ const gitlabToken = scmProvider === "gitlab"
510
+ ? credentials?.scmToken || config.gitlabToken
511
+ : config.gitlabToken;
435
512
  const envVars = {
436
513
  NODE_OPTIONS: "--max-old-space-size=3072",
437
514
  TASK_ID: task.id,
@@ -442,17 +519,17 @@ export async function spawnManagerWorker(task, config, credentials) {
442
519
  GITHUB_REPO: task.githubRepo || "",
443
520
  PR_URL: task.githubPrUrl || "",
444
521
  PR_NUMBER: task.githubPrNumber ? String(task.githubPrNumber) : "",
445
- // Cloud API
446
- API_BASE_URL: config.apiUrl,
522
+ // Cloud API — use containerApiUrl for Docker networking
523
+ API_BASE_URL: containerApiUrl,
447
524
  ORG_API_KEY: config.apiKey,
448
- // SCM configuration
525
+ // SCM configuration — org credentials from API are primary, local config is fallback
449
526
  SCM_PROVIDER: scmProvider,
450
527
  SCM_TOKEN: scmToken,
451
- GITHUB_TOKEN: config.githubToken,
452
- GH_TOKEN: config.githubToken,
453
- BITBUCKET_TOKEN: config.bitbucketToken,
454
- BITBUCKET_USERNAME: "x-token-auth",
455
- GITLAB_TOKEN: config.gitlabToken,
528
+ GITHUB_TOKEN: githubToken,
529
+ GH_TOKEN: githubToken,
530
+ BITBUCKET_TOKEN: bitbucketToken,
531
+ BITBUCKET_USERNAME: credentials?.bitbucketUsername || "x-token-auth",
532
+ GITLAB_TOKEN: gitlabToken,
456
533
  // Manager provider and model
457
534
  MANAGER_PROVIDER: credentials?.managerProvider || "anthropic",
458
535
  MANAGER_MODEL: credentials?.managerModelId || "",
@@ -473,8 +550,11 @@ export async function spawnManagerWorker(task, config, credentials) {
473
550
  dockerArgs.push("-e", `${k}=${v}`);
474
551
  }
475
552
  }
476
- // Worker image with manager entrypoint override
477
- const workerImage = config.workerImage || "public.ecr.aws/a7k5r0v0/workermill-worker:latest";
553
+ // Worker image with manager entrypoint override — private ECR requires auth
554
+ const workerImage = config.workerImage || `${PRIVATE_ECR_REGISTRY}/workermill-dev/worker:latest`;
555
+ if (workerImage.includes(PRIVATE_ECR_REGISTRY)) {
556
+ ensureEcrLogin();
557
+ }
478
558
  dockerArgs.push("--entrypoint", "/bin/bash");
479
559
  dockerArgs.push(workerImage);
480
560
  dockerArgs.push("/app/manager-entrypoint.sh");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workermill/agent",
3
- "version": "0.8.3",
3
+ "version": "0.8.8",
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",