@workermill/agent 0.7.5 → 0.7.7

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 CHANGED
@@ -17,7 +17,7 @@ export interface AgentConfig {
17
17
  gitlabToken: string;
18
18
  workerImage: string;
19
19
  teamPlanningEnabled: boolean;
20
- analystModel: string;
20
+ analystModel?: string;
21
21
  }
22
22
  export interface FileConfig {
23
23
  apiUrl: string;
package/dist/config.js CHANGED
@@ -76,7 +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
+ analystModel: fc.analystModel,
80
80
  };
81
81
  }
82
82
  /**
@@ -122,7 +122,7 @@ export function loadConfig() {
122
122
  gitlabToken: process.env.GITLAB_TOKEN || "",
123
123
  workerImage: process.env.WORKER_IMAGE || "workermill-worker:local",
124
124
  teamPlanningEnabled: process.env.TEAM_PLANNING_ENABLED !== "false",
125
- analystModel: process.env.ANALYST_MODEL || "sonnet",
125
+ analystModel: process.env.ANALYST_MODEL,
126
126
  };
127
127
  }
128
128
  /**
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
  });
@@ -603,7 +630,7 @@ export async function planTask(task, config, credentials) {
603
630
  });
604
631
  const { prompt: basePrompt, model, provider: planningProvider, maxStories: apiMaxStories } = promptResponse.data;
605
632
  const maxStories = typeof apiMaxStories === "number" ? apiMaxStories : 8;
606
- const cliModel = model || "sonnet";
633
+ const cliModel = model;
607
634
  const provider = (planningProvider || "anthropic");
608
635
  const isAnthropicPlanning = provider === "anthropic";
609
636
  const claudePath = process.env.CLAUDE_CLI_PATH || findClaudePath() || "claude";
@@ -633,7 +660,7 @@ export async function planTask(task, config, credentials) {
633
660
  console.log(`${ts()} ${taskLabel} ${chalk.yellow("⚠")} No SCM token for ${scmProvider}, skipping team planning`);
634
661
  }
635
662
  if (repoPath) {
636
- const analystModel = config.analystModel || "sonnet";
663
+ const analystModel = config.analystModel || cliModel;
637
664
  console.log(`${ts()} ${taskLabel} Analysts using model: ${chalk.yellow(analystModel)} (planner: ${chalk.yellow(cliModel)})`);
638
665
  const analysisResult = await runTeamAnalysis(task, basePrompt, claudePath, analystModel, cleanEnv, repoPath, task.id, startTime);
639
666
  if (analysisResult) {
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/dist/spawner.js CHANGED
@@ -182,9 +182,9 @@ export async function spawnWorker(task, config, orgConfig, credentials) {
182
182
  // Target repository
183
183
  TARGET_REPO: task.githubRepo || "",
184
184
  GITHUB_REPO: task.githubRepo || "",
185
- // Worker model (CLAUDE_MODEL is legacy compat for manager entrypoint)
186
- WORKER_MODEL: task.workerModel || String(orgConfig.defaultWorkerModel || "sonnet"),
187
- CLAUDE_MODEL: task.workerProvider === "anthropic" ? (task.workerModel || "sonnet") : "sonnet",
185
+ // Worker model comes from task or org settings, no hardcoded fallbacks
186
+ WORKER_MODEL: task.workerModel || String(orgConfig.defaultWorkerModel || ""),
187
+ CLAUDE_MODEL: task.workerModel || String(orgConfig.defaultWorkerModel || ""),
188
188
  // Jira credentials (from org Secrets Manager via /api/agent/claim)
189
189
  JIRA_BASE_URL: credentials?.jiraBaseUrl || "",
190
190
  JIRA_EMAIL: credentials?.jiraEmail || "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workermill/agent",
3
- "version": "0.7.5",
3
+ "version": "0.7.7",
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",