@workermill/agent 0.6.2 → 0.7.1
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 +2 -0
- package/dist/config.js +2 -0
- package/dist/plan-validator.d.ts +8 -0
- package/dist/plan-validator.js +22 -0
- package/dist/planner.js +75 -15
- package/package.json +1 -1
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
|
/**
|
package/dist/plan-validator.d.ts
CHANGED
|
@@ -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.
|
package/dist/plan-validator.js
CHANGED
|
@@ -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
|
/**
|
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;
|
|
@@ -154,10 +154,45 @@ function runClaudeCli(claudePath, model, prompt, env, taskId, startTime) {
|
|
|
154
154
|
continue;
|
|
155
155
|
try {
|
|
156
156
|
const event = JSON.parse(trimmed);
|
|
157
|
-
|
|
157
|
+
// Claude CLI stream-json wraps content in assistant message events
|
|
158
|
+
if (event.type === "assistant" && event.message?.content) {
|
|
159
|
+
const content = event.message.content;
|
|
160
|
+
if (Array.isArray(content)) {
|
|
161
|
+
for (const block of content) {
|
|
162
|
+
if (block.type === "text" && block.text) {
|
|
163
|
+
fullText += block.text;
|
|
164
|
+
charsReceived += block.text.length;
|
|
165
|
+
if (!firstTextSeen) {
|
|
166
|
+
firstTextSeen = true;
|
|
167
|
+
if (toolCallCount > 0 && !milestoneSent.analyzing) {
|
|
168
|
+
transitionPhase("analyzing");
|
|
169
|
+
milestoneSent.analyzing = true;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (charsReceived > 500 && !milestoneSent.generating) {
|
|
173
|
+
transitionPhase("generating_plan");
|
|
174
|
+
milestoneSent.generating = true;
|
|
175
|
+
lastProgressLogAt = Math.round((Date.now() - startTime) / 1000);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else if (block.type === "tool_use") {
|
|
179
|
+
toolCallCount++;
|
|
180
|
+
if (!milestoneSent.reading) {
|
|
181
|
+
transitionPhase("reading_repo");
|
|
182
|
+
milestoneSent.reading = true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else if (typeof content === "string" && content) {
|
|
188
|
+
fullText += content;
|
|
189
|
+
charsReceived += content.length;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else if (event.type === "content_block_delta" && event.delta?.text) {
|
|
193
|
+
// Fallback: raw API streaming format
|
|
158
194
|
fullText += event.delta.text;
|
|
159
195
|
charsReceived += event.delta.text.length;
|
|
160
|
-
// Phase: first text after tool calls → analyzing
|
|
161
196
|
if (!firstTextSeen) {
|
|
162
197
|
firstTextSeen = true;
|
|
163
198
|
if (toolCallCount > 0 && !milestoneSent.analyzing) {
|
|
@@ -165,7 +200,6 @@ function runClaudeCli(claudePath, model, prompt, env, taskId, startTime) {
|
|
|
165
200
|
milestoneSent.analyzing = true;
|
|
166
201
|
}
|
|
167
202
|
}
|
|
168
|
-
// Phase: substantial text → generating_plan
|
|
169
203
|
if (charsReceived > 500 && !milestoneSent.generating) {
|
|
170
204
|
transitionPhase("generating_plan");
|
|
171
205
|
milestoneSent.generating = true;
|
|
@@ -179,13 +213,6 @@ function runClaudeCli(claudePath, model, prompt, env, taskId, startTime) {
|
|
|
179
213
|
milestoneSent.reading = true;
|
|
180
214
|
}
|
|
181
215
|
}
|
|
182
|
-
else if (event.type === "assistant" && event.message?.content) {
|
|
183
|
-
const text = typeof event.message.content === "string" ? event.message.content : "";
|
|
184
|
-
if (text) {
|
|
185
|
-
fullText += text;
|
|
186
|
-
charsReceived += text.length;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
216
|
else if (event.type === "result" && event.result) {
|
|
190
217
|
resultText = typeof event.result === "string" ? event.result : "";
|
|
191
218
|
}
|
|
@@ -340,7 +367,27 @@ function runAnalyst(name, claudePath, model, prompt, repoPath, env, timeoutMs =
|
|
|
340
367
|
continue;
|
|
341
368
|
try {
|
|
342
369
|
const event = JSON.parse(trimmed);
|
|
343
|
-
|
|
370
|
+
// Claude CLI stream-json wraps content in assistant message events
|
|
371
|
+
if (event.type === "assistant" && event.message?.content) {
|
|
372
|
+
const content = event.message.content;
|
|
373
|
+
if (Array.isArray(content)) {
|
|
374
|
+
for (const block of content) {
|
|
375
|
+
if (block.type === "text" && block.text) {
|
|
376
|
+
fullText += block.text;
|
|
377
|
+
}
|
|
378
|
+
else if (block.type === "tool_use") {
|
|
379
|
+
toolCalls++;
|
|
380
|
+
const toolName = block.name || "unknown";
|
|
381
|
+
console.log(`${ts()} ${label} ${chalk.dim(`Tool: ${toolName}`)} (${toolCalls} total)`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
else if (typeof content === "string") {
|
|
386
|
+
fullText += content;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
else if (event.type === "content_block_delta" && event.delta?.text) {
|
|
390
|
+
// Fallback: raw API streaming format (may appear in some CLI versions)
|
|
344
391
|
fullText += event.delta.text;
|
|
345
392
|
}
|
|
346
393
|
else if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
|
|
@@ -546,7 +593,8 @@ export async function planTask(task, config, credentials) {
|
|
|
546
593
|
const promptResponse = await api.get("/api/agent/planning-prompt", {
|
|
547
594
|
params: { taskId: task.id },
|
|
548
595
|
});
|
|
549
|
-
const { prompt: basePrompt, model, provider: planningProvider } = promptResponse.data;
|
|
596
|
+
const { prompt: basePrompt, model, provider: planningProvider, maxStories: apiMaxStories } = promptResponse.data;
|
|
597
|
+
const maxStories = typeof apiMaxStories === "number" ? apiMaxStories : 8;
|
|
550
598
|
const cliModel = model || "sonnet";
|
|
551
599
|
const provider = (planningProvider || "anthropic");
|
|
552
600
|
const isAnthropicPlanning = provider === "anthropic";
|
|
@@ -577,7 +625,9 @@ export async function planTask(task, config, credentials) {
|
|
|
577
625
|
console.log(`${ts()} ${taskLabel} ${chalk.yellow("⚠")} No SCM token for ${scmProvider}, skipping team planning`);
|
|
578
626
|
}
|
|
579
627
|
if (repoPath) {
|
|
580
|
-
const
|
|
628
|
+
const analystModel = config.analystModel || "sonnet";
|
|
629
|
+
console.log(`${ts()} ${taskLabel} Analysts using model: ${chalk.yellow(analystModel)} (planner: ${chalk.yellow(cliModel)})`);
|
|
630
|
+
const analysisResult = await runTeamAnalysis(task, basePrompt, claudePath, analystModel, cleanEnv, repoPath, task.id, startTime);
|
|
581
631
|
if (analysisResult) {
|
|
582
632
|
enhancedBasePrompt = analysisResult;
|
|
583
633
|
}
|
|
@@ -656,7 +706,17 @@ export async function planTask(task, config, credentials) {
|
|
|
656
706
|
console.log(`${ts()} ${taskLabel} ${chalk.dim(detail)}`);
|
|
657
707
|
}
|
|
658
708
|
}
|
|
659
|
-
|
|
709
|
+
// 2c2. Apply story cap (max stories from org calibration)
|
|
710
|
+
const { droppedCount: storyDropCount, details: storyDropDetails } = applyStoryCap(plan, maxStories);
|
|
711
|
+
if (storyDropCount > 0) {
|
|
712
|
+
const msg = `${PREFIX} Story cap applied: ${storyDropCount} stories dropped (max ${maxStories})`;
|
|
713
|
+
console.log(`${ts()} ${taskLabel} ${chalk.yellow("⚠")} ${msg}`);
|
|
714
|
+
await postLog(task.id, msg);
|
|
715
|
+
for (const detail of storyDropDetails) {
|
|
716
|
+
console.log(`${ts()} ${taskLabel} ${chalk.dim(detail)}`);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
console.log(`${ts()} ${taskLabel} Plan: ${chalk.bold(plan.stories.length)} stories (max ${maxStories})`);
|
|
660
720
|
await postLog(task.id, `${PREFIX} Plan generated: ${plan.stories.length} stories (${formatElapsed(elapsed)}). Running critic validation...`);
|
|
661
721
|
// 2d. Run critic validation
|
|
662
722
|
const criticResult = await runCriticValidation(claudePath, cliModel, prd, plan, cleanEnv, taskLabel, provider, providerApiKey);
|