@workermill/agent 0.7.18 → 0.7.20
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/plan-validator.d.ts +2 -2
- package/dist/plan-validator.js +40 -4
- package/dist/planner.js +43 -9
- package/dist/poller.js +9 -4
- package/package.json +1 -1
package/dist/plan-validator.d.ts
CHANGED
|
@@ -90,7 +90,7 @@ export declare function parseCriticResponse(text: string): CriticResult;
|
|
|
90
90
|
* Run the critic via Claude CLI (lightweight — no tools, just reasoning).
|
|
91
91
|
* Returns the raw text output.
|
|
92
92
|
*/
|
|
93
|
-
export declare function runCriticCli(claudePath: string, model: string, prompt: string, env: Record<string, string | undefined
|
|
93
|
+
export declare function runCriticCli(claudePath: string, model: string, prompt: string, env: Record<string, string | undefined>, taskId?: string): Promise<string>;
|
|
94
94
|
/**
|
|
95
95
|
* Format critic feedback for appending to the planner prompt on re-run.
|
|
96
96
|
*/
|
|
@@ -100,5 +100,5 @@ export declare function formatCriticFeedback(critic: CriticResult): string;
|
|
|
100
100
|
* Routes to Claude CLI (Anthropic) or HTTP API (other providers).
|
|
101
101
|
* Returns the critic result, or null if critic fails (non-blocking).
|
|
102
102
|
*/
|
|
103
|
-
export declare function runCriticValidation(claudePath: string, model: string, prd: string, plan: ExecutionPlan, env: Record<string, string | undefined>, taskLabel: string, provider?: AIProvider, providerApiKey?: string): Promise<CriticResult | null>;
|
|
103
|
+
export declare function runCriticValidation(claudePath: string, model: string, prd: string, plan: ExecutionPlan, env: Record<string, string | undefined>, taskLabel: string, provider?: AIProvider, providerApiKey?: string, taskId?: string): Promise<CriticResult | null>;
|
|
104
104
|
export { AUTO_APPROVAL_THRESHOLD };
|
package/dist/plan-validator.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { spawn } from "child_process";
|
|
13
13
|
import chalk from "chalk";
|
|
14
14
|
import { generateText } from "./providers.js";
|
|
15
|
+
import { api } from "./api.js";
|
|
15
16
|
// ============================================================================
|
|
16
17
|
// CONSTANTS
|
|
17
18
|
// ============================================================================
|
|
@@ -277,7 +278,7 @@ export function parseCriticResponse(text) {
|
|
|
277
278
|
* Run the critic via Claude CLI (lightweight — no tools, just reasoning).
|
|
278
279
|
* Returns the raw text output.
|
|
279
280
|
*/
|
|
280
|
-
export function runCriticCli(claudePath, model, prompt, env) {
|
|
281
|
+
export function runCriticCli(claudePath, model, prompt, env, taskId) {
|
|
281
282
|
return new Promise((resolve, reject) => {
|
|
282
283
|
const proc = spawn(claudePath, [
|
|
283
284
|
"--print",
|
|
@@ -294,7 +295,21 @@ export function runCriticCli(claudePath, model, prompt, env) {
|
|
|
294
295
|
let stdout = "";
|
|
295
296
|
let stderr = "";
|
|
296
297
|
proc.stdout.on("data", (data) => {
|
|
297
|
-
|
|
298
|
+
const chunk = data.toString();
|
|
299
|
+
stdout += chunk;
|
|
300
|
+
// Stream critic reasoning to dashboard in real-time
|
|
301
|
+
const lines = chunk.split("\n").filter((l) => l.trim());
|
|
302
|
+
for (const line of lines) {
|
|
303
|
+
const trimmed = line.trim().length > 200
|
|
304
|
+
? line.trim().substring(0, 200) + "…"
|
|
305
|
+
: line.trim();
|
|
306
|
+
if (trimmed) {
|
|
307
|
+
if (taskId) {
|
|
308
|
+
postLog(taskId, `${PREFIX} [critic] ${trimmed}`, "output");
|
|
309
|
+
}
|
|
310
|
+
console.log(`${ts()} ${chalk.dim("🔍")} ${chalk.dim(trimmed)}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
298
313
|
});
|
|
299
314
|
proc.stderr.on("data", (data) => {
|
|
300
315
|
stderr += data.toString();
|
|
@@ -358,23 +373,44 @@ export function formatCriticFeedback(critic) {
|
|
|
358
373
|
lines.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.");
|
|
359
374
|
return lines.join("\n");
|
|
360
375
|
}
|
|
376
|
+
/** Consistent prefix matching planner dashboard format */
|
|
377
|
+
const PREFIX = "[🗺️ planning_agent 🤖]";
|
|
361
378
|
/** Timestamp prefix for console logs */
|
|
362
379
|
function ts() {
|
|
363
380
|
return chalk.dim(new Date().toLocaleTimeString());
|
|
364
381
|
}
|
|
382
|
+
/**
|
|
383
|
+
* Post a log message to the cloud dashboard for real-time visibility.
|
|
384
|
+
*/
|
|
385
|
+
async function postLog(taskId, message, type = "system", severity = "info") {
|
|
386
|
+
try {
|
|
387
|
+
await api.post("/api/control-center/logs", {
|
|
388
|
+
taskId,
|
|
389
|
+
type,
|
|
390
|
+
message,
|
|
391
|
+
severity,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
// Fire and forget — don't block critic on log failures
|
|
396
|
+
}
|
|
397
|
+
}
|
|
365
398
|
/**
|
|
366
399
|
* Run critic validation on a parsed plan.
|
|
367
400
|
* Routes to Claude CLI (Anthropic) or HTTP API (other providers).
|
|
368
401
|
* Returns the critic result, or null if critic fails (non-blocking).
|
|
369
402
|
*/
|
|
370
|
-
export async function runCriticValidation(claudePath, model, prd, plan, env, taskLabel, provider, providerApiKey) {
|
|
403
|
+
export async function runCriticValidation(claudePath, model, prd, plan, env, taskLabel, provider, providerApiKey, taskId) {
|
|
371
404
|
const criticPrompt = buildCriticPrompt(prd, plan);
|
|
372
405
|
const effectiveProvider = provider || "anthropic";
|
|
373
406
|
console.log(`${ts()} ${taskLabel} ${chalk.dim(`Running critic validation (${effectiveProvider})...`)}`);
|
|
407
|
+
if (taskId) {
|
|
408
|
+
postLog(taskId, `${PREFIX} Running critic validation (${effectiveProvider})...`);
|
|
409
|
+
}
|
|
374
410
|
try {
|
|
375
411
|
let rawCriticOutput;
|
|
376
412
|
if (effectiveProvider === "anthropic") {
|
|
377
|
-
rawCriticOutput = await runCriticCli(claudePath, model, criticPrompt, env);
|
|
413
|
+
rawCriticOutput = await runCriticCli(claudePath, model, criticPrompt, env, taskId);
|
|
378
414
|
}
|
|
379
415
|
else {
|
|
380
416
|
if (!providerApiKey) {
|
package/dist/planner.js
CHANGED
|
@@ -70,20 +70,50 @@ const MAX_ITERATIONS = 3;
|
|
|
70
70
|
function ts() {
|
|
71
71
|
return chalk.dim(new Date().toLocaleTimeString());
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Log queue — sends entries sequentially instead of N concurrent POSTs.
|
|
75
|
+
* During planning, flushTextBuffer() can fire 15-30 postLog() calls in a burst.
|
|
76
|
+
* Without queuing, those concurrent POSTs saturate the API's DB connection pool
|
|
77
|
+
* (max 10), causing poll timeouts, transient 401s, and multi-second stalls.
|
|
78
|
+
*/
|
|
79
|
+
const logQueue = [];
|
|
80
|
+
let logDrainPromise = null;
|
|
81
|
+
async function drainLogQueue() {
|
|
82
|
+
while (logQueue.length > 0) {
|
|
83
|
+
const entry = logQueue.shift();
|
|
84
|
+
try {
|
|
85
|
+
await api.post("/api/control-center/logs", entry, { timeout: 5_000 });
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Best-effort — drop on failure
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
73
92
|
/**
|
|
74
93
|
* Post a log message to the cloud dashboard for real-time visibility.
|
|
94
|
+
* Entries are queued and drained sequentially (max 1 in-flight POST).
|
|
75
95
|
*/
|
|
76
96
|
async function postLog(taskId, message, type = "system", severity = "info") {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
97
|
+
if (logQueue.length >= 200)
|
|
98
|
+
logQueue.shift(); // drop oldest
|
|
99
|
+
logQueue.push({ taskId, message, type, severity });
|
|
100
|
+
if (!logDrainPromise) {
|
|
101
|
+
logDrainPromise = drainLogQueue().finally(() => {
|
|
102
|
+
logDrainPromise = null;
|
|
83
103
|
});
|
|
84
104
|
}
|
|
85
|
-
|
|
86
|
-
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Flush remaining log entries (call before cleanup).
|
|
108
|
+
*/
|
|
109
|
+
async function flushLogQueue() {
|
|
110
|
+
if (logDrainPromise)
|
|
111
|
+
await logDrainPromise;
|
|
112
|
+
if (logQueue.length > 0) {
|
|
113
|
+
logDrainPromise = drainLogQueue().finally(() => {
|
|
114
|
+
logDrainPromise = null;
|
|
115
|
+
});
|
|
116
|
+
await logDrainPromise;
|
|
87
117
|
}
|
|
88
118
|
}
|
|
89
119
|
/**
|
|
@@ -238,6 +268,7 @@ function runClaudeCli(claudePath, model, prompt, env, taskId, startTime, disable
|
|
|
238
268
|
if (block.type === "text" && block.text) {
|
|
239
269
|
fullText += block.text;
|
|
240
270
|
charsReceived += block.text.length;
|
|
271
|
+
textBuffer += block.text;
|
|
241
272
|
if (!firstTextSeen) {
|
|
242
273
|
firstTextSeen = true;
|
|
243
274
|
if (toolCallCount > 0 && !milestoneSent.analyzing) {
|
|
@@ -263,6 +294,7 @@ function runClaudeCli(claudePath, model, prompt, env, taskId, startTime, disable
|
|
|
263
294
|
else if (typeof content === "string" && content) {
|
|
264
295
|
fullText += content;
|
|
265
296
|
charsReceived += content.length;
|
|
297
|
+
textBuffer += content;
|
|
266
298
|
}
|
|
267
299
|
}
|
|
268
300
|
else if (event.type === "content_block_delta" && event.delta?.text) {
|
|
@@ -919,7 +951,7 @@ export async function planTask(task, config, credentials) {
|
|
|
919
951
|
console.log(`${ts()} ${taskLabel} Plan: ${chalk.bold(plan.stories.length)} stories (max ${maxStories})`);
|
|
920
952
|
await postLog(task.id, `${PREFIX} Plan generated: ${plan.stories.length} stories (${formatElapsed(elapsed)}). Running critic validation...`);
|
|
921
953
|
// 2d. Run critic validation
|
|
922
|
-
const criticResult = await runCriticValidation(claudePath, cliModel, prd, plan, cleanEnv, taskLabel, provider, providerApiKey);
|
|
954
|
+
const criticResult = await runCriticValidation(claudePath, cliModel, prd, plan, cleanEnv, taskLabel, provider, providerApiKey, task.id);
|
|
923
955
|
// Track best plan across iterations
|
|
924
956
|
if (criticResult && criticResult.score > bestScore) {
|
|
925
957
|
bestPlan = plan;
|
|
@@ -1036,6 +1068,8 @@ export async function planTask(task, config, credentials) {
|
|
|
1036
1068
|
return false;
|
|
1037
1069
|
}
|
|
1038
1070
|
finally {
|
|
1071
|
+
// Drain any remaining log entries before cleanup
|
|
1072
|
+
await flushLogQueue();
|
|
1039
1073
|
// Cleanup temp clone
|
|
1040
1074
|
if (repoPath) {
|
|
1041
1075
|
try {
|
package/dist/poller.js
CHANGED
|
@@ -75,12 +75,17 @@ async function pollOnce(config) {
|
|
|
75
75
|
}
|
|
76
76
|
catch (error) {
|
|
77
77
|
const err = error;
|
|
78
|
+
const busy = planningInProgress.size > 0 || getActiveCount() > 0 || managerInProgress.size > 0;
|
|
78
79
|
if (err.response?.status === 401) {
|
|
79
|
-
|
|
80
|
+
if (!busy) {
|
|
81
|
+
console.error(`${ts()} ${chalk.red("✗")} Authentication failed. Check your API key.`);
|
|
82
|
+
}
|
|
83
|
+
// Silent when busy — transient DB pool exhaustion on server
|
|
80
84
|
}
|
|
81
|
-
else {
|
|
82
|
-
console.
|
|
85
|
+
else if (!busy) {
|
|
86
|
+
console.warn(`${ts()} ${chalk.yellow("⚠")} Poll error: ${err.message || String(error)}`);
|
|
83
87
|
}
|
|
88
|
+
// Silent when busy — expected during heavy planning/execution
|
|
84
89
|
}
|
|
85
90
|
}
|
|
86
91
|
/**
|
|
@@ -181,7 +186,7 @@ async function handleQueuedTask(task, config) {
|
|
|
181
186
|
}
|
|
182
187
|
catch (err) {
|
|
183
188
|
const taskLabel = chalk.cyan(task.id.slice(0, 8));
|
|
184
|
-
console.
|
|
189
|
+
console.warn(`${ts()} ${chalk.yellow("⚠")} Failed to report started for ${taskLabel}`);
|
|
185
190
|
}
|
|
186
191
|
const taskLabel = chalk.cyan(task.id.slice(0, 8));
|
|
187
192
|
console.log();
|