@workermill/agent 0.7.3 → 0.7.6

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 CHANGED
@@ -103,6 +103,24 @@ function runClaudeCli(claudePath, model, prompt, env, taskId, startTime) {
103
103
  let stderrOutput = "";
104
104
  let charsReceived = 0;
105
105
  let toolCallCount = 0;
106
+ // Buffered text streaming — flush complete lines to dashboard every 1s.
107
+ // LLM deltas are tiny fragments; we accumulate until we see '\n', then
108
+ // a 1s interval flushes all complete lines as log entries. On exit we
109
+ // flush whatever remains (including any incomplete trailing line).
110
+ let textBuffer = "";
111
+ function flushTextBuffer(final = false) {
112
+ if (!textBuffer)
113
+ return;
114
+ const parts = textBuffer.split("\n");
115
+ // Keep the incomplete trailing fragment unless this is the final flush
116
+ const incomplete = final ? "" : (parts.pop() || "");
117
+ for (const line of parts) {
118
+ if (line.trim()) {
119
+ postLog(taskId, `${PREFIX} ${line}`, "output");
120
+ }
121
+ }
122
+ textBuffer = incomplete;
123
+ }
106
124
  // Phase detection state
107
125
  let currentPhase = "initializing";
108
126
  let firstTextSeen = false;
@@ -117,6 +135,8 @@ function runClaudeCli(claudePath, model, prompt, env, taskId, startTime) {
117
135
  postLog(taskId, msg);
118
136
  console.log(`${ts()} ${taskLabel} ${chalk.dim(msg)}`);
119
137
  }
138
+ // Flush buffered LLM text to dashboard every 1s (complete lines only)
139
+ const textFlushInterval = setInterval(() => flushTextBuffer(), 1_000);
120
140
  // SSE progress updates every 2s — drives PlanningTerminalBar in dashboard
121
141
  // (same cadence as local dev's progressInterval in planning-agent-local.ts)
122
142
  const sseProgressInterval = setInterval(() => {
@@ -193,6 +213,7 @@ function runClaudeCli(claudePath, model, prompt, env, taskId, startTime) {
193
213
  // Fallback: raw API streaming format
194
214
  fullText += event.delta.text;
195
215
  charsReceived += event.delta.text.length;
216
+ textBuffer += event.delta.text;
196
217
  if (!firstTextSeen) {
197
218
  firstTextSeen = true;
198
219
  if (toolCallCount > 0 && !milestoneSent.analyzing) {
@@ -230,6 +251,8 @@ function runClaudeCli(claudePath, model, prompt, env, taskId, startTime) {
230
251
  const timeout = setTimeout(() => {
231
252
  clearInterval(progressInterval);
232
253
  clearInterval(sseProgressInterval);
254
+ clearInterval(textFlushInterval);
255
+ flushTextBuffer(true);
233
256
  proc.kill("SIGTERM");
234
257
  reject(new Error("Claude CLI timed out after 20 minutes"));
235
258
  }, 1_200_000);
@@ -237,6 +260,8 @@ function runClaudeCli(claudePath, model, prompt, env, taskId, startTime) {
237
260
  clearTimeout(timeout);
238
261
  clearInterval(progressInterval);
239
262
  clearInterval(sseProgressInterval);
263
+ clearInterval(textFlushInterval);
264
+ flushTextBuffer(true);
240
265
  // Emit final "validating" phase to dashboard
241
266
  const elapsedAtClose = Math.round((Date.now() - startTime) / 1000);
242
267
  postProgress(taskId, "validating", elapsedAtClose, "Validating plan...", charsReceived, toolCallCount);
@@ -252,6 +277,8 @@ function runClaudeCli(claudePath, model, prompt, env, taskId, startTime) {
252
277
  clearTimeout(timeout);
253
278
  clearInterval(progressInterval);
254
279
  clearInterval(sseProgressInterval);
280
+ clearInterval(textFlushInterval);
281
+ flushTextBuffer(true);
255
282
  reject(err);
256
283
  });
257
284
  });
package/dist/poller.js CHANGED
@@ -89,6 +89,7 @@ async function pollOnce(config) {
89
89
  async function handlePlanningTask(task, config) {
90
90
  // Claim the task (also returns org credentials for provider API keys)
91
91
  let credentials;
92
+ let claimedTask;
92
93
  try {
93
94
  const claimResponse = await api.post("/api/agent/claim", {
94
95
  taskId: task.id,
@@ -98,11 +99,39 @@ async function handlePlanningTask(task, config) {
98
99
  return; // Another agent or cloud orchestrator claimed it
99
100
  }
100
101
  credentials = claimResponse.data.credentials;
102
+ claimedTask = claimResponse.data.task;
101
103
  }
102
104
  catch {
103
105
  return;
104
106
  }
105
107
  const taskLabel = chalk.cyan(task.id.slice(0, 8));
108
+ // Check if this is a retry with an existing plan (resume scenario).
109
+ // The API preserves planJson/executionPlanV2 on retry when stories exist,
110
+ // so we can skip planning entirely and transition straight to queued.
111
+ const isRetryWithPlan = claimedTask &&
112
+ (claimedTask.retryCount ?? 0) > 0 &&
113
+ claimedTask.executionPlanV2 != null;
114
+ if (isRetryWithPlan) {
115
+ console.log();
116
+ console.log(`${ts()} ${chalk.magenta("◆ RESUME")} ${taskLabel} ${task.summary.substring(0, 60)}`);
117
+ console.log(`${ts()} ${taskLabel} Retry #${claimedTask.retryCount} with existing plan — skipping planning`);
118
+ planningInProgress.add(task.id);
119
+ // Tell the API to resume with the existing plan (planning → queued)
120
+ try {
121
+ await api.post("/api/agent/resume-plan", {
122
+ taskId: task.id,
123
+ agentId: config.agentId,
124
+ });
125
+ console.log(`${ts()} ${taskLabel} ${chalk.green("✓")} Resumed with existing plan → ${chalk.green("queued")}`);
126
+ }
127
+ catch (err) {
128
+ const error = err;
129
+ const detail = error.response?.data?.error || error.message || String(err);
130
+ console.error(`${ts()} ${taskLabel} ${chalk.red("✗")} Resume failed: ${detail}`);
131
+ }
132
+ planningInProgress.delete(task.id);
133
+ return;
134
+ }
106
135
  console.log();
107
136
  console.log(`${ts()} ${chalk.magenta("◆ PLANNING")} ${taskLabel} ${task.summary.substring(0, 60)}`);
108
137
  planningInProgress.add(task.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workermill/agent",
3
- "version": "0.7.3",
3
+ "version": "0.7.6",
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",