@workermill/agent 0.7.8 โ 0.7.9
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/planner.js +92 -21
- package/package.json +1 -1
package/dist/planner.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* sees the same planning progress as cloud mode.
|
|
16
16
|
*/
|
|
17
17
|
import chalk from "chalk";
|
|
18
|
+
import ora from "ora";
|
|
18
19
|
import { spawn, execSync } from "child_process";
|
|
19
20
|
import { findClaudePath } from "./config.js";
|
|
20
21
|
import { api } from "./api.js";
|
|
@@ -103,6 +104,23 @@ function runClaudeCli(claudePath, model, prompt, env, taskId, startTime) {
|
|
|
103
104
|
let stderrOutput = "";
|
|
104
105
|
let charsReceived = 0;
|
|
105
106
|
let toolCallCount = 0;
|
|
107
|
+
// Live spinner โ shows elapsed time, phase, and chars generated
|
|
108
|
+
const spinner = ora({
|
|
109
|
+
text: `${taskLabel} Initializing planner...`,
|
|
110
|
+
prefixText: "",
|
|
111
|
+
spinner: "dots",
|
|
112
|
+
}).start();
|
|
113
|
+
function updateSpinner() {
|
|
114
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
115
|
+
const phaseIcon = currentPhase === "reading_repo" ? "๐" :
|
|
116
|
+
currentPhase === "analyzing" ? "๐" :
|
|
117
|
+
currentPhase === "generating_plan" ? "๐" :
|
|
118
|
+
currentPhase === "validating" ? "โ
" : "โณ";
|
|
119
|
+
const stats = chalk.dim(`${formatElapsed(elapsed)} ยท ${charsReceived} chars ยท ${toolCallCount} tools`);
|
|
120
|
+
spinner.text = `${taskLabel} ${phaseIcon} ${phaseLabel(currentPhase, elapsed)} ${stats}`;
|
|
121
|
+
}
|
|
122
|
+
// Update spinner every 500ms for smooth elapsed time display
|
|
123
|
+
const spinnerInterval = setInterval(updateSpinner, 500);
|
|
106
124
|
// Buffered text streaming โ flush complete lines to dashboard every 1s.
|
|
107
125
|
// LLM deltas are tiny fragments; we accumulate until we see '\n', then
|
|
108
126
|
// a 1s interval flushes all complete lines as log entries. On exit we
|
|
@@ -117,6 +135,11 @@ function runClaudeCli(claudePath, model, prompt, env, taskId, startTime) {
|
|
|
117
135
|
for (const line of parts) {
|
|
118
136
|
if (line.trim()) {
|
|
119
137
|
postLog(taskId, `${PREFIX} ${line}`, "output");
|
|
138
|
+
// Echo planner thoughts to local terminal
|
|
139
|
+
spinner.stop();
|
|
140
|
+
const truncated = line.trim().length > 160 ? line.trim().substring(0, 160) + "โฆ" : line.trim();
|
|
141
|
+
console.log(`${ts()} ${taskLabel} ${chalk.dim("๐ญ")} ${chalk.dim(truncated)}`);
|
|
142
|
+
spinner.start();
|
|
120
143
|
}
|
|
121
144
|
}
|
|
122
145
|
textBuffer = incomplete;
|
|
@@ -133,7 +156,10 @@ function runClaudeCli(claudePath, model, prompt, env, taskId, startTime) {
|
|
|
133
156
|
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
134
157
|
const msg = phaseLabel(newPhase, elapsed);
|
|
135
158
|
postLog(taskId, msg);
|
|
159
|
+
spinner.stop();
|
|
136
160
|
console.log(`${ts()} ${taskLabel} ${chalk.dim(msg)}`);
|
|
161
|
+
spinner.start();
|
|
162
|
+
updateSpinner();
|
|
137
163
|
}
|
|
138
164
|
// Flush buffered LLM text to dashboard every 1s (complete lines only)
|
|
139
165
|
const textFlushInterval = setInterval(() => flushTextBuffer(), 1_000);
|
|
@@ -159,7 +185,9 @@ function runClaudeCli(claudePath, model, prompt, env, taskId, startTime) {
|
|
|
159
185
|
lastProgressLogAt = elapsed;
|
|
160
186
|
const msg = `${PREFIX} Planning in progress โ analyzing requirements and decomposing into steps (${formatElapsed(elapsed)} elapsed)`;
|
|
161
187
|
postLog(taskId, msg);
|
|
188
|
+
spinner.stop();
|
|
162
189
|
console.log(`${ts()} ${taskLabel} ${chalk.dim(msg)}`);
|
|
190
|
+
spinner.start();
|
|
163
191
|
}
|
|
164
192
|
}, 5_000);
|
|
165
193
|
// Parse streaming JSON lines from Claude CLI
|
|
@@ -248,20 +276,22 @@ function runClaudeCli(claudePath, model, prompt, env, taskId, startTime) {
|
|
|
248
276
|
proc.stderr.on("data", (chunk) => {
|
|
249
277
|
stderrOutput += chunk.toString();
|
|
250
278
|
});
|
|
251
|
-
|
|
279
|
+
function cleanupAll() {
|
|
252
280
|
clearInterval(progressInterval);
|
|
253
281
|
clearInterval(sseProgressInterval);
|
|
254
282
|
clearInterval(textFlushInterval);
|
|
283
|
+
clearInterval(spinnerInterval);
|
|
255
284
|
flushTextBuffer(true);
|
|
285
|
+
spinner.stop();
|
|
286
|
+
}
|
|
287
|
+
const timeout = setTimeout(() => {
|
|
288
|
+
cleanupAll();
|
|
256
289
|
proc.kill("SIGTERM");
|
|
257
290
|
reject(new Error("Claude CLI timed out after 20 minutes"));
|
|
258
291
|
}, 1_200_000);
|
|
259
292
|
proc.on("exit", (code) => {
|
|
260
293
|
clearTimeout(timeout);
|
|
261
|
-
|
|
262
|
-
clearInterval(sseProgressInterval);
|
|
263
|
-
clearInterval(textFlushInterval);
|
|
264
|
-
flushTextBuffer(true);
|
|
294
|
+
cleanupAll();
|
|
265
295
|
// Emit final "validating" phase to dashboard
|
|
266
296
|
const elapsedAtClose = Math.round((Date.now() - startTime) / 1000);
|
|
267
297
|
postProgress(taskId, "validating", elapsedAtClose, "Validating plan...", charsReceived, toolCallCount);
|
|
@@ -275,10 +305,7 @@ function runClaudeCli(claudePath, model, prompt, env, taskId, startTime) {
|
|
|
275
305
|
});
|
|
276
306
|
proc.on("error", (err) => {
|
|
277
307
|
clearTimeout(timeout);
|
|
278
|
-
|
|
279
|
-
clearInterval(sseProgressInterval);
|
|
280
|
-
clearInterval(textFlushInterval);
|
|
281
|
-
flushTextBuffer(true);
|
|
308
|
+
cleanupAll();
|
|
282
309
|
reject(err);
|
|
283
310
|
});
|
|
284
311
|
});
|
|
@@ -351,10 +378,14 @@ async function cloneTargetRepo(repo, token, scmProvider, taskId) {
|
|
|
351
378
|
* Run an analyst agent via Claude CLI with tool access to the cloned repo.
|
|
352
379
|
* Returns the analyst's report text, or an empty string on failure.
|
|
353
380
|
*/
|
|
354
|
-
function runAnalyst(name, claudePath, model, prompt, repoPath, env, timeoutMs = 900_000) {
|
|
381
|
+
function runAnalyst(name, claudePath, model, prompt, repoPath, env, timeoutMs = 900_000, taskId) {
|
|
355
382
|
const label = chalk.blue(`[${name}]`);
|
|
383
|
+
const modelLabel = chalk.yellow(model);
|
|
356
384
|
return new Promise((resolve) => {
|
|
357
|
-
console.log(`${ts()} ${label} Starting
|
|
385
|
+
console.log(`${ts()} ${label} Starting analyst using ${modelLabel}...`);
|
|
386
|
+
if (taskId) {
|
|
387
|
+
postLog(taskId, `${PREFIX} [${name}] Starting analyst using ${model}...`);
|
|
388
|
+
}
|
|
358
389
|
const proc = spawn(claudePath, [
|
|
359
390
|
"--print",
|
|
360
391
|
"--verbose",
|
|
@@ -376,12 +407,23 @@ function runAnalyst(name, claudePath, model, prompt, repoPath, env, timeoutMs =
|
|
|
376
407
|
let toolCalls = 0;
|
|
377
408
|
let timedOut = false;
|
|
378
409
|
const startMs = Date.now();
|
|
410
|
+
// Live spinner for this analyst
|
|
411
|
+
const analystSpinner = ora({
|
|
412
|
+
text: `${label} Starting (${model})...`,
|
|
413
|
+
spinner: "dots",
|
|
414
|
+
}).start();
|
|
415
|
+
const analystSpinnerInterval = setInterval(() => {
|
|
416
|
+
const elapsed = Math.round((Date.now() - startMs) / 1000);
|
|
417
|
+
analystSpinner.text = `${label} ${chalk.dim(`${formatElapsed(elapsed)} ยท ${toolCalls} tools ยท ${fullText.length} chars`)}`;
|
|
418
|
+
}, 500);
|
|
379
419
|
proc.stderr.on("data", (chunk) => {
|
|
380
420
|
const text = chunk.toString();
|
|
381
421
|
stderrOutput += text;
|
|
382
422
|
// Show stderr in real-time so we can see what's happening
|
|
383
423
|
for (const line of text.split("\n").filter((l) => l.trim())) {
|
|
424
|
+
analystSpinner.stop();
|
|
384
425
|
console.log(`${ts()} ${label} ${chalk.red("stderr:")} ${line.trim()}`);
|
|
426
|
+
analystSpinner.start();
|
|
385
427
|
}
|
|
386
428
|
});
|
|
387
429
|
proc.stdout.on("data", (data) => {
|
|
@@ -404,7 +446,11 @@ function runAnalyst(name, claudePath, model, prompt, repoPath, env, timeoutMs =
|
|
|
404
446
|
// Log analyst reasoning (first line, truncated)
|
|
405
447
|
const thought = block.text.trim().split("\n")[0].substring(0, 120);
|
|
406
448
|
if (thought) {
|
|
449
|
+
analystSpinner.stop();
|
|
407
450
|
console.log(`${ts()} ${label} ${chalk.dim("๐ญ")} ${chalk.dim(thought)}`);
|
|
451
|
+
if (taskId)
|
|
452
|
+
postLog(taskId, `${PREFIX} [${name}] ๐ญ ${thought}`);
|
|
453
|
+
analystSpinner.start();
|
|
408
454
|
}
|
|
409
455
|
}
|
|
410
456
|
else if (block.type === "tool_use") {
|
|
@@ -413,7 +459,11 @@ function runAnalyst(name, claudePath, model, prompt, repoPath, env, timeoutMs =
|
|
|
413
459
|
// Show tool name + input preview (file path, pattern, etc.)
|
|
414
460
|
const inputStr = block.input ? JSON.stringify(block.input) : "";
|
|
415
461
|
const inputPreview = inputStr.length > 80 ? inputStr.substring(0, 80) + "โฆ" : inputStr;
|
|
416
|
-
|
|
462
|
+
analystSpinner.stop();
|
|
463
|
+
console.log(`${ts()} ${label} ${chalk.dim(`Tool: ${toolName}`)}${inputPreview ? chalk.dim(` ${inputPreview}`) : ""}`);
|
|
464
|
+
if (taskId)
|
|
465
|
+
postLog(taskId, `${PREFIX} [${name}] Tool: ${toolName} ${inputPreview}`);
|
|
466
|
+
analystSpinner.start();
|
|
417
467
|
}
|
|
418
468
|
}
|
|
419
469
|
}
|
|
@@ -428,7 +478,11 @@ function runAnalyst(name, claudePath, model, prompt, repoPath, env, timeoutMs =
|
|
|
428
478
|
else if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
|
|
429
479
|
toolCalls++;
|
|
430
480
|
const toolName = event.content_block?.name || "unknown";
|
|
431
|
-
|
|
481
|
+
analystSpinner.stop();
|
|
482
|
+
console.log(`${ts()} ${label} ${chalk.dim(`Tool: ${toolName}`)}`);
|
|
483
|
+
if (taskId)
|
|
484
|
+
postLog(taskId, `${PREFIX} [${name}] Tool: ${toolName}`);
|
|
485
|
+
analystSpinner.start();
|
|
432
486
|
}
|
|
433
487
|
else if (event.type === "result" && event.result) {
|
|
434
488
|
resultText =
|
|
@@ -442,30 +496,44 @@ function runAnalyst(name, claudePath, model, prompt, repoPath, env, timeoutMs =
|
|
|
442
496
|
});
|
|
443
497
|
const timeout = setTimeout(() => {
|
|
444
498
|
timedOut = true;
|
|
499
|
+
clearInterval(analystSpinnerInterval);
|
|
500
|
+
analystSpinner.stop();
|
|
445
501
|
proc.kill("SIGTERM");
|
|
446
502
|
const elapsed = Math.round((Date.now() - startMs) / 1000);
|
|
447
|
-
console.log(`${ts()} ${label} ${chalk.yellow("โ Timed out")} after ${elapsed}
|
|
503
|
+
console.log(`${ts()} ${label} ${chalk.yellow("โ Timed out")} after ${formatElapsed(elapsed)} (${toolCalls} tool calls, ${fullText.length} chars)`);
|
|
504
|
+
if (taskId)
|
|
505
|
+
postLog(taskId, `${PREFIX} [${name}] โ Timed out after ${formatElapsed(elapsed)}`);
|
|
448
506
|
resolve(resultText || fullText || "");
|
|
449
507
|
}, timeoutMs);
|
|
450
508
|
proc.on("exit", (code) => {
|
|
451
509
|
clearTimeout(timeout);
|
|
510
|
+
clearInterval(analystSpinnerInterval);
|
|
511
|
+
analystSpinner.stop();
|
|
452
512
|
const elapsed = Math.round((Date.now() - startMs) / 1000);
|
|
453
513
|
if (timedOut)
|
|
454
514
|
return; // already resolved
|
|
455
515
|
const output = resultText || fullText || "";
|
|
456
516
|
if (code === 0 && output.length > 0) {
|
|
457
|
-
console.log(`${ts()} ${label} ${chalk.green("โ Done")} in ${elapsed}
|
|
517
|
+
console.log(`${ts()} ${label} ${chalk.green("โ Done")} in ${formatElapsed(elapsed)} (${toolCalls} tool calls, ${output.length} chars)`);
|
|
518
|
+
if (taskId)
|
|
519
|
+
postLog(taskId, `${PREFIX} [${name}] โ Done in ${formatElapsed(elapsed)} (${toolCalls} tool calls, ${output.length} chars)`);
|
|
458
520
|
}
|
|
459
521
|
else if (code !== 0) {
|
|
460
|
-
console.log(`${ts()} ${label} ${chalk.red(`โ Exited ${code}`)} after ${elapsed}
|
|
522
|
+
console.log(`${ts()} ${label} ${chalk.red(`โ Exited ${code}`)} after ${formatElapsed(elapsed)} โ ${stderrOutput.substring(0, 150) || "no stderr"}`);
|
|
523
|
+
if (taskId)
|
|
524
|
+
postLog(taskId, `${PREFIX} [${name}] โ Exited ${code} after ${formatElapsed(elapsed)}`);
|
|
461
525
|
}
|
|
462
526
|
else {
|
|
463
|
-
console.log(`${ts()} ${label} ${chalk.yellow("โ Empty output")} after ${elapsed}
|
|
527
|
+
console.log(`${ts()} ${label} ${chalk.yellow("โ Empty output")} after ${formatElapsed(elapsed)} (${toolCalls} tool calls)`);
|
|
528
|
+
if (taskId)
|
|
529
|
+
postLog(taskId, `${PREFIX} [${name}] โ Empty output after ${formatElapsed(elapsed)}`);
|
|
464
530
|
}
|
|
465
531
|
resolve(output);
|
|
466
532
|
});
|
|
467
533
|
proc.on("error", (err) => {
|
|
468
534
|
clearTimeout(timeout);
|
|
535
|
+
clearInterval(analystSpinnerInterval);
|
|
536
|
+
analystSpinner.stop();
|
|
469
537
|
console.log(`${ts()} ${label} ${chalk.red("โ Spawn failed:")} ${err.message}`);
|
|
470
538
|
resolve("");
|
|
471
539
|
});
|
|
@@ -476,10 +544,13 @@ function runAnalyst(name, claudePath, model, prompt, repoPath, env, timeoutMs =
|
|
|
476
544
|
* Used for non-Anthropic providers (OpenAI, Google, Ollama) that can't use Claude CLI.
|
|
477
545
|
* Returns the analyst's report text, or an empty string on failure.
|
|
478
546
|
*/
|
|
479
|
-
async function runAnalystWithSdk(name, provider, model, apiKey, prompt, repoPath, timeoutMs = 900_000) {
|
|
547
|
+
async function runAnalystWithSdk(name, provider, model, apiKey, prompt, repoPath, timeoutMs = 900_000, taskId) {
|
|
480
548
|
const label = chalk.blue(`[${name}]`);
|
|
549
|
+
const modelLabel = chalk.yellow(`${provider}/${model}`);
|
|
481
550
|
const startMs = Date.now();
|
|
482
|
-
console.log(`${ts()} ${label} Starting via AI SDK
|
|
551
|
+
console.log(`${ts()} ${label} Starting analyst using ${modelLabel} via AI SDK...`);
|
|
552
|
+
if (taskId)
|
|
553
|
+
postLog(taskId, `${PREFIX} [${name}] Starting analyst using ${provider}/${model} via AI SDK...`);
|
|
483
554
|
try {
|
|
484
555
|
const result = await generateTextWithTools({
|
|
485
556
|
provider,
|
|
@@ -594,13 +665,13 @@ async function runTeamAnalysis(task, basePrompt, claudePath, model, env, repoPat
|
|
|
594
665
|
// Helper: dispatch analyst to Claude CLI or AI SDK based on provider
|
|
595
666
|
const dispatchAnalyst = (name, prompt) => {
|
|
596
667
|
if (useCliAnalysts) {
|
|
597
|
-
return runAnalyst(name, claudePath, analysisModel, prompt, repoPath, env);
|
|
668
|
+
return runAnalyst(name, claudePath, analysisModel, prompt, repoPath, env, 900_000, taskId);
|
|
598
669
|
}
|
|
599
670
|
if (!providerApiKey) {
|
|
600
671
|
console.log(`${ts()} ${taskLabel} ${chalk.yellow("โ ")} No API key for ${provider} analysts, skipping ${name}`);
|
|
601
672
|
return Promise.resolve("");
|
|
602
673
|
}
|
|
603
|
-
return runAnalystWithSdk(name, provider, analysisModel, providerApiKey, prompt, repoPath);
|
|
674
|
+
return runAnalystWithSdk(name, provider, analysisModel, providerApiKey, prompt, repoPath, 900_000, taskId);
|
|
604
675
|
};
|
|
605
676
|
let codebaseReport = "";
|
|
606
677
|
let requirementsReport = "";
|