@workermill/agent 0.6.1 → 0.7.0

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/config.d.ts CHANGED
@@ -17,6 +17,7 @@ export interface AgentConfig {
17
17
  gitlabToken: string;
18
18
  workerImage: string;
19
19
  teamPlanningEnabled: boolean;
20
+ analystModel: string;
20
21
  }
21
22
  export interface FileConfig {
22
23
  apiUrl: string;
@@ -32,6 +33,7 @@ export interface FileConfig {
32
33
  };
33
34
  workerImage: string;
34
35
  teamPlanningEnabled?: boolean;
36
+ analystModel?: string;
35
37
  setupCompletedAt: string;
36
38
  }
37
39
  export declare function getConfigDir(): string;
package/dist/config.js CHANGED
@@ -76,6 +76,7 @@ export function loadConfigFromFile() {
76
76
  gitlabToken: fc.tokens?.gitlab || "",
77
77
  workerImage,
78
78
  teamPlanningEnabled: fc.teamPlanningEnabled ?? true,
79
+ analystModel: fc.analystModel || "sonnet",
79
80
  };
80
81
  }
81
82
  /**
@@ -121,6 +122,7 @@ export function loadConfig() {
121
122
  gitlabToken: process.env.GITLAB_TOKEN || "",
122
123
  workerImage: process.env.WORKER_IMAGE || "workermill-worker:local",
123
124
  teamPlanningEnabled: process.env.TEAM_PLANNING_ENABLED !== "false",
125
+ analystModel: process.env.ANALYST_MODEL || "sonnet",
124
126
  };
125
127
  }
126
128
  /**
@@ -53,6 +53,14 @@ export declare function applyFileCap(plan: ExecutionPlan): {
53
53
  truncatedCount: number;
54
54
  details: string[];
55
55
  };
56
+ /**
57
+ * Apply story cap to the plan. Truncates stories beyond maxStories.
58
+ * Returns details about dropped stories for logging.
59
+ */
60
+ export declare function applyStoryCap(plan: ExecutionPlan, maxStories: number): {
61
+ droppedCount: number;
62
+ details: string[];
63
+ };
56
64
  /**
57
65
  * Re-serialize plan as a JSON code block for posting to the API.
58
66
  * The server-side parseExecutionPlan() expects ```json ... ``` blocks.
@@ -59,6 +59,28 @@ export function applyFileCap(plan) {
59
59
  return { truncatedCount, details };
60
60
  }
61
61
  // ============================================================================
62
+ // STORY CAP
63
+ // ============================================================================
64
+ /**
65
+ * Apply story cap to the plan. Truncates stories beyond maxStories.
66
+ * Returns details about dropped stories for logging.
67
+ */
68
+ export function applyStoryCap(plan, maxStories) {
69
+ if (plan.stories.length <= maxStories) {
70
+ return { droppedCount: 0, details: [] };
71
+ }
72
+ const droppedCount = plan.stories.length - maxStories;
73
+ const dropped = plan.stories.slice(maxStories);
74
+ const details = dropped.map((s) => `${s.id}: "${s.title}" (${s.persona})`);
75
+ plan.stories = plan.stories.slice(0, maxStories);
76
+ // Fix dependencies that reference dropped stories
77
+ const validIds = new Set(plan.stories.map((s) => s.id));
78
+ for (const story of plan.stories) {
79
+ story.dependencies = story.dependencies.filter((dep) => validIds.has(dep));
80
+ }
81
+ return { droppedCount, details };
82
+ }
83
+ // ============================================================================
62
84
  // PLAN SERIALIZATION
63
85
  // ============================================================================
64
86
  /**
@@ -183,8 +205,8 @@ export function runCriticCli(claudePath, model, prompt, env) {
183
205
  });
184
206
  const timeout = setTimeout(() => {
185
207
  proc.kill("SIGTERM");
186
- reject(new Error("Critic CLI timed out after 10 minutes"));
187
- }, 600_000);
208
+ reject(new Error("Critic CLI timed out after 20 minutes"));
209
+ }, 1_200_000);
188
210
  proc.on("exit", (code) => {
189
211
  clearTimeout(timeout);
190
212
  if (code !== 0) {
@@ -262,7 +284,7 @@ export async function runCriticValidation(claudePath, model, prd, plan, env, tas
262
284
  if (!providerApiKey) {
263
285
  throw new Error(`No API key for critic provider "${effectiveProvider}"`);
264
286
  }
265
- rawCriticOutput = await generateText(effectiveProvider, model, criticPrompt, providerApiKey, { maxTokens: 4096, temperature: 0.3, timeoutMs: 600_000 });
287
+ rawCriticOutput = await generateText(effectiveProvider, model, criticPrompt, providerApiKey, { maxTokens: 4096, temperature: 0.3, timeoutMs: 1_200_000 });
266
288
  }
267
289
  const result = parseCriticResponse(rawCriticOutput);
268
290
  const statusIcon = result.score >= AUTO_APPROVAL_THRESHOLD
package/dist/planner.js CHANGED
@@ -18,7 +18,7 @@ import chalk from "chalk";
18
18
  import { spawn, execSync } from "child_process";
19
19
  import { findClaudePath } from "./config.js";
20
20
  import { api } from "./api.js";
21
- import { parseExecutionPlan, applyFileCap, serializePlan, runCriticValidation, formatCriticFeedback, AUTO_APPROVAL_THRESHOLD, } from "./plan-validator.js";
21
+ import { parseExecutionPlan, applyFileCap, applyStoryCap, serializePlan, runCriticValidation, formatCriticFeedback, AUTO_APPROVAL_THRESHOLD, } from "./plan-validator.js";
22
22
  import { generateText } from "./providers.js";
23
23
  /** Max Planner-Critic iterations before giving up */
24
24
  const MAX_ITERATIONS = 3;
@@ -546,7 +546,8 @@ export async function planTask(task, config, credentials) {
546
546
  const promptResponse = await api.get("/api/agent/planning-prompt", {
547
547
  params: { taskId: task.id },
548
548
  });
549
- const { prompt: basePrompt, model, provider: planningProvider } = promptResponse.data;
549
+ const { prompt: basePrompt, model, provider: planningProvider, maxStories: apiMaxStories } = promptResponse.data;
550
+ const maxStories = typeof apiMaxStories === "number" ? apiMaxStories : 8;
550
551
  const cliModel = model || "sonnet";
551
552
  const provider = (planningProvider || "anthropic");
552
553
  const isAnthropicPlanning = provider === "anthropic";
@@ -577,7 +578,9 @@ export async function planTask(task, config, credentials) {
577
578
  console.log(`${ts()} ${taskLabel} ${chalk.yellow("⚠")} No SCM token for ${scmProvider}, skipping team planning`);
578
579
  }
579
580
  if (repoPath) {
580
- const analysisResult = await runTeamAnalysis(task, basePrompt, claudePath, cliModel, cleanEnv, repoPath, task.id, startTime);
581
+ const analystModel = config.analystModel || "sonnet";
582
+ console.log(`${ts()} ${taskLabel} Analysts using model: ${chalk.yellow(analystModel)} (planner: ${chalk.yellow(cliModel)})`);
583
+ const analysisResult = await runTeamAnalysis(task, basePrompt, claudePath, analystModel, cleanEnv, repoPath, task.id, startTime);
581
584
  if (analysisResult) {
582
585
  enhancedBasePrompt = analysisResult;
583
586
  }
@@ -656,7 +659,17 @@ export async function planTask(task, config, credentials) {
656
659
  console.log(`${ts()} ${taskLabel} ${chalk.dim(detail)}`);
657
660
  }
658
661
  }
659
- console.log(`${ts()} ${taskLabel} Plan: ${chalk.bold(plan.stories.length)} stories`);
662
+ // 2c2. Apply story cap (max stories from org calibration)
663
+ const { droppedCount: storyDropCount, details: storyDropDetails } = applyStoryCap(plan, maxStories);
664
+ if (storyDropCount > 0) {
665
+ const msg = `${PREFIX} Story cap applied: ${storyDropCount} stories dropped (max ${maxStories})`;
666
+ console.log(`${ts()} ${taskLabel} ${chalk.yellow("⚠")} ${msg}`);
667
+ await postLog(task.id, msg);
668
+ for (const detail of storyDropDetails) {
669
+ console.log(`${ts()} ${taskLabel} ${chalk.dim(detail)}`);
670
+ }
671
+ }
672
+ console.log(`${ts()} ${taskLabel} Plan: ${chalk.bold(plan.stories.length)} stories (max ${maxStories})`);
660
673
  await postLog(task.id, `${PREFIX} Plan generated: ${plan.stories.length} stories (${formatElapsed(elapsed)}). Running critic validation...`);
661
674
  // 2d. Run critic validation
662
675
  const criticResult = await runCriticValidation(claudePath, cliModel, prd, plan, cleanEnv, taskLabel, provider, providerApiKey);
package/dist/poller.js CHANGED
@@ -250,7 +250,11 @@ export function startPolling(config) {
250
250
  */
251
251
  export function startHeartbeat(config) {
252
252
  setInterval(async () => {
253
- const activeTaskIds = getActiveTaskIds();
253
+ // Include BOTH running containers AND tasks being planned/managed
254
+ const containerTaskIds = getActiveTaskIds();
255
+ const planningTaskIds = Array.from(planningInProgress);
256
+ const managerTaskIds = Array.from(managerInProgress);
257
+ const activeTaskIds = [...containerTaskIds, ...planningTaskIds, ...managerTaskIds];
254
258
  try {
255
259
  const response = await api.post("/api/agent/heartbeat", {
256
260
  agentId: config.agentId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workermill/agent",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
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",