@workermill/agent 0.4.3 → 0.4.6

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.
@@ -39,7 +39,7 @@ export interface CriticResult {
39
39
  suggestedChanges?: string[];
40
40
  }>;
41
41
  }
42
- declare const AUTO_APPROVAL_THRESHOLD = 85;
42
+ declare const AUTO_APPROVAL_THRESHOLD = 75;
43
43
  /**
44
44
  * Parse execution plan JSON from raw Claude CLI output.
45
45
  * Mirrors server-side parseExecutionPlan() in planning-agent-local.ts.
@@ -16,7 +16,7 @@ import { generateText } from "./providers.js";
16
16
  // CONSTANTS
17
17
  // ============================================================================
18
18
  const MAX_TARGET_FILES = 5;
19
- const AUTO_APPROVAL_THRESHOLD = 85;
19
+ const AUTO_APPROVAL_THRESHOLD = 75;
20
20
  // ============================================================================
21
21
  // PLAN PARSING
22
22
  // ============================================================================
package/dist/planner.js CHANGED
@@ -297,8 +297,10 @@ async function cloneTargetRepo(repo, token, scmProvider, taskId) {
297
297
  * Run an analyst agent via Claude CLI with tool access to the cloned repo.
298
298
  * Returns the analyst's report text, or an empty string on failure.
299
299
  */
300
- function runAnalyst(claudePath, model, prompt, repoPath, env, timeoutMs = 120_000) {
300
+ function runAnalyst(name, claudePath, model, prompt, repoPath, env, timeoutMs = 900_000) {
301
+ const label = chalk.blue(`[${name}]`);
301
302
  return new Promise((resolve) => {
303
+ console.log(`${ts()} ${label} Starting (${chalk.dim(model)})...`);
302
304
  const proc = spawn(claudePath, [
303
305
  "--print",
304
306
  "--verbose",
@@ -310,15 +312,23 @@ function runAnalyst(claudePath, model, prompt, repoPath, env, timeoutMs = 120_00
310
312
  env,
311
313
  stdio: ["pipe", "pipe", "pipe"],
312
314
  });
313
- // Write prompt via stdin (same as runClaudeCli — not via -p arg)
315
+ // Write prompt via stdin (same as runClaudeCli)
314
316
  proc.stdin.write(prompt);
315
317
  proc.stdin.end();
316
318
  let resultText = "";
317
319
  let fullText = "";
318
320
  let stderrOutput = "";
319
321
  let lineBuffer = "";
322
+ let toolCalls = 0;
323
+ let timedOut = false;
324
+ const startMs = Date.now();
320
325
  proc.stderr.on("data", (chunk) => {
321
- stderrOutput += chunk.toString();
326
+ const text = chunk.toString();
327
+ stderrOutput += text;
328
+ // Show stderr in real-time so we can see what's happening
329
+ for (const line of text.split("\n").filter((l) => l.trim())) {
330
+ console.log(`${ts()} ${label} ${chalk.red("stderr:")} ${line.trim()}`);
331
+ }
322
332
  });
323
333
  proc.stdout.on("data", (data) => {
324
334
  lineBuffer += data.toString();
@@ -333,6 +343,11 @@ function runAnalyst(claudePath, model, prompt, repoPath, env, timeoutMs = 120_00
333
343
  if (event.type === "content_block_delta" && event.delta?.text) {
334
344
  fullText += event.delta.text;
335
345
  }
346
+ else if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
347
+ toolCalls++;
348
+ const toolName = event.content_block?.name || "unknown";
349
+ console.log(`${ts()} ${label} ${chalk.dim(`Tool: ${toolName}`)} (${toolCalls} total)`);
350
+ }
336
351
  else if (event.type === "result" && event.result) {
337
352
  resultText =
338
353
  typeof event.result === "string" ? event.result : "";
@@ -344,19 +359,32 @@ function runAnalyst(claudePath, model, prompt, repoPath, env, timeoutMs = 120_00
344
359
  }
345
360
  });
346
361
  const timeout = setTimeout(() => {
362
+ timedOut = true;
347
363
  proc.kill("SIGTERM");
364
+ const elapsed = Math.round((Date.now() - startMs) / 1000);
365
+ console.log(`${ts()} ${label} ${chalk.yellow("⚠ Timed out")} after ${elapsed}s (${toolCalls} tool calls, ${fullText.length} chars)`);
348
366
  resolve(resultText || fullText || "");
349
367
  }, timeoutMs);
350
368
  proc.on("exit", (code) => {
351
369
  clearTimeout(timeout);
352
- if (code !== 0 && stderrOutput) {
353
- console.error(`${chalk.yellow("⚠")} Analyst exited with code ${code}: ${stderrOutput.substring(0, 200)}`);
370
+ const elapsed = Math.round((Date.now() - startMs) / 1000);
371
+ if (timedOut)
372
+ return; // already resolved
373
+ const output = resultText || fullText || "";
374
+ if (code === 0 && output.length > 0) {
375
+ console.log(`${ts()} ${label} ${chalk.green("✓ Done")} in ${elapsed}s (${toolCalls} tool calls, ${output.length} chars)`);
354
376
  }
355
- resolve(resultText || fullText || "");
377
+ else if (code !== 0) {
378
+ console.log(`${ts()} ${label} ${chalk.red(`✗ Exited ${code}`)} after ${elapsed}s — ${stderrOutput.substring(0, 150) || "no stderr"}`);
379
+ }
380
+ else {
381
+ console.log(`${ts()} ${label} ${chalk.yellow("⚠ Empty output")} after ${elapsed}s (${toolCalls} tool calls)`);
382
+ }
383
+ resolve(output);
356
384
  });
357
385
  proc.on("error", (err) => {
358
386
  clearTimeout(timeout);
359
- console.error(`${chalk.yellow("")} Analyst spawn error: ${err.message}`);
387
+ console.log(`${ts()} ${label} ${chalk.red("✗ Spawn failed:")} ${err.message}`);
360
388
  resolve("");
361
389
  });
362
390
  });
@@ -411,19 +439,44 @@ async function runTeamPlanning(task, basePrompt, claudePath, model, env, repoPat
411
439
  await postLog(taskId, `${PREFIX} Team planning: running codebase, requirements, and risk analysts in parallel...`);
412
440
  await postProgress(taskId, "reading_repo", Math.round((Date.now() - startTime) / 1000), "Running parallel analysis agents...", 0, 0);
413
441
  const analysisModel = model.includes("opus") ? "sonnet" : model;
414
- const [codebaseResult, requirementsResult, riskResult] = await Promise.allSettled([
415
- runAnalyst(claudePath, analysisModel, CODEBASE_ANALYST_PROMPT, repoPath, env),
416
- runAnalyst(claudePath, analysisModel, makeRequirementsAnalystPrompt(task), repoPath, env),
417
- runAnalyst(claudePath, analysisModel, makeRiskAssessorPrompt(task), repoPath, env),
418
- ]);
419
- const codebaseReport = codebaseResult.status === "fulfilled" ? codebaseResult.value : "";
420
- const requirementsReport = requirementsResult.status === "fulfilled" ? requirementsResult.value : "";
421
- const riskReport = riskResult.status === "fulfilled" ? riskResult.value : "";
422
- const successCount = [codebaseReport, requirementsReport, riskReport].filter((r) => r.length > 0).length;
423
- const analysisElapsed = Math.round((Date.now() - startTime) / 1000);
424
- console.log(`${ts()} ${taskLabel} ${chalk.green("")} Analysis complete: ${successCount}/3 reports (${analysisElapsed}s)`);
425
- await postLog(taskId, `${PREFIX} Team analysis complete: ${successCount}/3 reports in ${formatElapsed(analysisElapsed)}. Synthesizing plan...`);
426
- await postProgress(taskId, "analyzing", analysisElapsed, "Synthesizing analysis reports...", 0, 0);
442
+ const MAX_TEAM_RETRIES = 3;
443
+ let codebaseReport = "";
444
+ let requirementsReport = "";
445
+ let riskReport = "";
446
+ for (let attempt = 1; attempt <= MAX_TEAM_RETRIES; attempt++) {
447
+ if (attempt > 1) {
448
+ console.log(`${ts()} ${taskLabel} ${chalk.magenta("◆ Team planning")} retry ${attempt}/${MAX_TEAM_RETRIES}...`);
449
+ await postLog(taskId, `${PREFIX} Team analysis retry ${attempt}/${MAX_TEAM_RETRIES}...`);
450
+ }
451
+ const [codebaseResult, requirementsResult, riskResult] = await Promise.allSettled([
452
+ codebaseReport ? Promise.resolve(codebaseReport) : runAnalyst("Codebase", claudePath, analysisModel, CODEBASE_ANALYST_PROMPT, repoPath, env),
453
+ requirementsReport ? Promise.resolve(requirementsReport) : runAnalyst("Requirements", claudePath, analysisModel, makeRequirementsAnalystPrompt(task), repoPath, env),
454
+ riskReport ? Promise.resolve(riskReport) : runAnalyst("Risk", claudePath, analysisModel, makeRiskAssessorPrompt(task), repoPath, env),
455
+ ]);
456
+ if (!codebaseReport && codebaseResult.status === "fulfilled") {
457
+ codebaseReport = codebaseResult.value;
458
+ }
459
+ if (!requirementsReport && requirementsResult.status === "fulfilled") {
460
+ requirementsReport = requirementsResult.value;
461
+ }
462
+ if (!riskReport && riskResult.status === "fulfilled") {
463
+ riskReport = riskResult.value;
464
+ }
465
+ const successCount = [codebaseReport, requirementsReport, riskReport].filter((r) => r.length > 0).length;
466
+ const analysisElapsed = Math.round((Date.now() - startTime) / 1000);
467
+ console.log(`${ts()} ${taskLabel} Analysis attempt ${attempt}: ${successCount}/3 reports (${analysisElapsed}s)`);
468
+ if (successCount > 0) {
469
+ console.log(`${ts()} ${taskLabel} ${chalk.green("✓")} Analysis complete: ${successCount}/3 reports (${analysisElapsed}s)`);
470
+ await postLog(taskId, `${PREFIX} Team analysis complete: ${successCount}/3 reports in ${formatElapsed(analysisElapsed)}. Synthesizing plan...`);
471
+ await postProgress(taskId, "analyzing", analysisElapsed, "Synthesizing analysis reports...", 0, 0);
472
+ break;
473
+ }
474
+ if (attempt === MAX_TEAM_RETRIES) {
475
+ console.log(`${ts()} ${taskLabel} ${chalk.yellow("⚠")} All analysts failed after ${MAX_TEAM_RETRIES} attempts, falling back to single-agent planning`);
476
+ await postLog(taskId, `${PREFIX} All analysis agents failed after ${MAX_TEAM_RETRIES} attempts — falling back to single-agent planning`);
477
+ return runClaudeCli(claudePath, model, basePrompt, env, taskId, startTime);
478
+ }
479
+ }
427
480
  // Build enhanced prompt with analysis reports
428
481
  const sections = [];
429
482
  if (codebaseReport) {
@@ -435,12 +488,6 @@ async function runTeamPlanning(task, basePrompt, claudePath, model, env, repoPat
435
488
  if (riskReport) {
436
489
  sections.push(`## Risk Assessment\n\n${riskReport}`);
437
490
  }
438
- if (sections.length === 0) {
439
- // All analysts failed — fall through to regular planning
440
- console.log(`${ts()} ${taskLabel} ${chalk.yellow("⚠")} All analysts failed, falling back to single-agent planning`);
441
- await postLog(taskId, `${PREFIX} All analysis agents failed — falling back to single-agent planning`);
442
- return runClaudeCli(claudePath, model, basePrompt, env, taskId, startTime);
443
- }
444
491
  const enhancedPrompt = basePrompt +
445
492
  "\n\n" +
446
493
  sections.join("\n\n") +
@@ -520,7 +567,7 @@ export async function planTask(task, config, credentials) {
520
567
  // 2a. Generate plan via Claude CLI (Anthropic) or HTTP API (other providers)
521
568
  let rawOutput;
522
569
  try {
523
- if (isAnthropicPlanning && config.teamPlanningEnabled && repoPath && iteration === 1) {
570
+ if (isAnthropicPlanning && config.teamPlanningEnabled && repoPath) {
524
571
  rawOutput = await runTeamPlanning(task, currentPrompt, claudePath, cliModel, cleanEnv, repoPath, task.id, startTime);
525
572
  }
526
573
  else if (isAnthropicPlanning) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workermill/agent",
3
- "version": "0.4.3",
3
+ "version": "0.4.6",
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",