@workermill/agent 0.7.0 → 0.7.2
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/commands/start.js +15 -1
- package/dist/index.js +3 -1
- package/dist/planner.js +66 -11
- package/dist/poller.d.ts +5 -0
- package/dist/poller.js +19 -2
- package/package.json +1 -1
package/dist/commands/start.js
CHANGED
|
@@ -105,9 +105,23 @@ export async function startCommand(options) {
|
|
|
105
105
|
const cleanup = await startAgent(config);
|
|
106
106
|
// Write PID file for status command
|
|
107
107
|
writeFileSync(getPidFile(), String(process.pid), "utf-8");
|
|
108
|
-
// Graceful shutdown
|
|
108
|
+
// Graceful shutdown with force-exit safety net
|
|
109
|
+
let shuttingDown = false;
|
|
109
110
|
const shutdown = async () => {
|
|
111
|
+
if (shuttingDown) {
|
|
112
|
+
// Double Ctrl+C → force exit immediately
|
|
113
|
+
console.log(chalk.red("\n Force exit."));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
shuttingDown = true;
|
|
117
|
+
// Force exit after 10s if cleanup hangs
|
|
118
|
+
const forceTimer = setTimeout(() => {
|
|
119
|
+
console.log(chalk.red("\n Cleanup timed out. Force exit."));
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}, 10_000);
|
|
122
|
+
forceTimer.unref(); // Don't keep process alive just for this timer
|
|
110
123
|
await cleanup();
|
|
124
|
+
clearTimeout(forceTimer);
|
|
111
125
|
try {
|
|
112
126
|
unlinkSync(getPidFile());
|
|
113
127
|
}
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import chalk from "chalk";
|
|
8
8
|
import { initApi, api } from "./api.js";
|
|
9
|
-
import { startPolling, startHeartbeat } from "./poller.js";
|
|
9
|
+
import { startPolling, startHeartbeat, stopPolling } from "./poller.js";
|
|
10
10
|
import { stopAll } from "./spawner.js";
|
|
11
11
|
import { AGENT_VERSION } from "./version.js";
|
|
12
12
|
import { selfUpdate, restartAgent } from "./updater.js";
|
|
@@ -82,6 +82,8 @@ export async function startAgent(config) {
|
|
|
82
82
|
return async () => {
|
|
83
83
|
console.log();
|
|
84
84
|
console.log(chalk.dim(" Shutting down..."));
|
|
85
|
+
// Stop poll/heartbeat loops first so nothing re-fires during cleanup
|
|
86
|
+
stopPolling();
|
|
85
87
|
try {
|
|
86
88
|
await api.post("/api/agent/deregister", { agentId: config.agentId });
|
|
87
89
|
}
|
package/dist/planner.js
CHANGED
|
@@ -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,35 @@ 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
|
+
// Log analyst reasoning (first line, truncated)
|
|
378
|
+
const thought = block.text.trim().split("\n")[0].substring(0, 120);
|
|
379
|
+
if (thought) {
|
|
380
|
+
console.log(`${ts()} ${label} ${chalk.dim("💭")} ${chalk.dim(thought)}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
else if (block.type === "tool_use") {
|
|
384
|
+
toolCalls++;
|
|
385
|
+
const toolName = block.name || "unknown";
|
|
386
|
+
// Show tool name + input preview (file path, pattern, etc.)
|
|
387
|
+
const inputStr = block.input ? JSON.stringify(block.input) : "";
|
|
388
|
+
const inputPreview = inputStr.length > 80 ? inputStr.substring(0, 80) + "…" : inputStr;
|
|
389
|
+
console.log(`${ts()} ${label} ${chalk.dim(`Tool: ${toolName}`)}${inputPreview ? chalk.dim(` ${inputPreview}`) : ""} (${toolCalls} total)`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else if (typeof content === "string") {
|
|
394
|
+
fullText += content;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
else if (event.type === "content_block_delta" && event.delta?.text) {
|
|
398
|
+
// Fallback: raw API streaming format (may appear in some CLI versions)
|
|
344
399
|
fullText += event.delta.text;
|
|
345
400
|
}
|
|
346
401
|
else if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
|
package/dist/poller.d.ts
CHANGED
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
* dispatch to planner or spawner based on task status.
|
|
6
6
|
*/
|
|
7
7
|
import type { AgentConfig } from "./config.js";
|
|
8
|
+
/**
|
|
9
|
+
* Stop all polling and heartbeat loops.
|
|
10
|
+
* Called during graceful shutdown to prevent the event loop from staying alive.
|
|
11
|
+
*/
|
|
12
|
+
export declare function stopPolling(): void;
|
|
8
13
|
/**
|
|
9
14
|
* Start the poll loop.
|
|
10
15
|
*/
|
package/dist/poller.js
CHANGED
|
@@ -235,6 +235,23 @@ async function handleManagerTask(task, config) {
|
|
|
235
235
|
console.error(`${ts()} ${taskLabel} ${chalk.red("✗")} Failed to claim manager task:`, error.message || String(err));
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
|
+
// Store interval IDs so they can be cleared on shutdown
|
|
239
|
+
let pollIntervalId = null;
|
|
240
|
+
let heartbeatIntervalId = null;
|
|
241
|
+
/**
|
|
242
|
+
* Stop all polling and heartbeat loops.
|
|
243
|
+
* Called during graceful shutdown to prevent the event loop from staying alive.
|
|
244
|
+
*/
|
|
245
|
+
export function stopPolling() {
|
|
246
|
+
if (pollIntervalId) {
|
|
247
|
+
clearInterval(pollIntervalId);
|
|
248
|
+
pollIntervalId = null;
|
|
249
|
+
}
|
|
250
|
+
if (heartbeatIntervalId) {
|
|
251
|
+
clearInterval(heartbeatIntervalId);
|
|
252
|
+
heartbeatIntervalId = null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
238
255
|
/**
|
|
239
256
|
* Start the poll loop.
|
|
240
257
|
*/
|
|
@@ -243,13 +260,13 @@ export function startPolling(config) {
|
|
|
243
260
|
// Initial poll
|
|
244
261
|
pollOnce(config);
|
|
245
262
|
// Recurring poll
|
|
246
|
-
setInterval(() => pollOnce(config), config.pollIntervalMs);
|
|
263
|
+
pollIntervalId = setInterval(() => pollOnce(config), config.pollIntervalMs);
|
|
247
264
|
}
|
|
248
265
|
/**
|
|
249
266
|
* Start the heartbeat loop.
|
|
250
267
|
*/
|
|
251
268
|
export function startHeartbeat(config) {
|
|
252
|
-
setInterval(async () => {
|
|
269
|
+
heartbeatIntervalId = setInterval(async () => {
|
|
253
270
|
// Include BOTH running containers AND tasks being planned/managed
|
|
254
271
|
const containerTaskIds = getActiveTaskIds();
|
|
255
272
|
const planningTaskIds = Array.from(planningInProgress);
|