workermill 0.3.3 → 0.4.1

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.
@@ -3162,6 +3162,42 @@ var CostTracker = class {
3162
3162
  }
3163
3163
  };
3164
3164
 
3165
+ // src/logger.ts
3166
+ init_esm_shims();
3167
+ import fs9 from "fs";
3168
+ import path11 from "path";
3169
+ var LOG_DIR = path11.join(process.cwd(), ".workermill");
3170
+ var LOG_FILE = path11.join(LOG_DIR, "cli.log");
3171
+ var logStream = null;
3172
+ function ensureLogDir() {
3173
+ if (!fs9.existsSync(LOG_DIR)) {
3174
+ fs9.mkdirSync(LOG_DIR, { recursive: true });
3175
+ }
3176
+ }
3177
+ function getStream() {
3178
+ if (!logStream) {
3179
+ ensureLogDir();
3180
+ logStream = fs9.createWriteStream(LOG_FILE, { flags: "a" });
3181
+ }
3182
+ return logStream;
3183
+ }
3184
+ function timestamp() {
3185
+ return (/* @__PURE__ */ new Date()).toISOString();
3186
+ }
3187
+ function log(level, message, data) {
3188
+ const entry = data ? `[${timestamp()}] ${level}: ${message} ${JSON.stringify(data)}` : `[${timestamp()}] ${level}: ${message}`;
3189
+ getStream().write(entry + "\n");
3190
+ }
3191
+ function info(message, data) {
3192
+ log("INFO", message, data);
3193
+ }
3194
+ function error(message, data) {
3195
+ log("ERROR", message, data);
3196
+ }
3197
+ function debug(message, data) {
3198
+ log("DEBUG", message, data);
3199
+ }
3200
+
3165
3201
  export {
3166
3202
  __dirname,
3167
3203
  init_esm_shims,
@@ -3172,5 +3208,8 @@ export {
3172
3208
  createModel,
3173
3209
  killActiveProcess,
3174
3210
  createToolDefinitions,
3175
- CostTracker
3211
+ CostTracker,
3212
+ info,
3213
+ error,
3214
+ debug
3176
3215
  };
package/dist/index.js CHANGED
@@ -4,12 +4,15 @@ import {
4
4
  buildOllamaOptions,
5
5
  createModel,
6
6
  createToolDefinitions,
7
+ debug,
8
+ error,
7
9
  getProviderForPersona,
10
+ info,
8
11
  init_esm_shims,
9
12
  killActiveProcess,
10
13
  loadConfig,
11
14
  saveConfig
12
- } from "./chunk-KL7SFKGG.js";
15
+ } from "./chunk-77CAVW3Y.js";
13
16
 
14
17
  // src/index.ts
15
18
  init_esm_shims();
@@ -24,101 +27,312 @@ import readline from "readline";
24
27
  import { execSync } from "child_process";
25
28
  import chalk from "chalk";
26
29
  var PROVIDERS = [
27
- { name: "ollama", display: "Ollama (local, no API key needed)", needsKey: false, defaultModel: "qwen3-coder:30b" },
28
- { name: "anthropic", display: "Anthropic (Claude)", needsKey: true, defaultModel: "claude-sonnet-4-6", envVar: "ANTHROPIC_API_KEY" },
29
- { name: "openai", display: "OpenAI (GPT)", needsKey: true, defaultModel: "gpt-5.4", envVar: "OPENAI_API_KEY" },
30
- { name: "google", display: "Google (Gemini)", needsKey: true, defaultModel: "gemini-3.1-pro", envVar: "GOOGLE_GENERATIVE_AI_API_KEY" }
30
+ {
31
+ name: "ollama",
32
+ display: "Ollama (local, no API key)",
33
+ needsKey: false,
34
+ models: [
35
+ { id: "qwen3-coder:30b", label: "Qwen 3 Coder 30B (recommended)" },
36
+ { id: "qwen2.5-coder:32b", label: "Qwen 2.5 Coder 32B" }
37
+ ]
38
+ },
39
+ {
40
+ name: "anthropic",
41
+ display: "Anthropic (Claude)",
42
+ needsKey: true,
43
+ envVar: "ANTHROPIC_API_KEY",
44
+ models: [
45
+ { id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6 (recommended)" },
46
+ { id: "claude-opus-4-6", label: "Claude Opus 4.6 (most powerful)" }
47
+ ]
48
+ },
49
+ {
50
+ name: "openai",
51
+ display: "OpenAI",
52
+ needsKey: true,
53
+ envVar: "OPENAI_API_KEY",
54
+ models: [
55
+ { id: "gpt-5.4", label: "GPT-5.4 (latest flagship)" },
56
+ { id: "gpt-5.3-codex", label: "GPT-5.3 Codex (built for code)" }
57
+ ]
58
+ },
59
+ {
60
+ name: "google",
61
+ display: "Google (Gemini)",
62
+ needsKey: true,
63
+ envVar: "GOOGLE_GENERATIVE_AI_API_KEY",
64
+ models: [
65
+ { id: "gemini-2.5-pro", label: "Gemini 2.5 Pro (most powerful)" },
66
+ { id: "gemini-2.5-flash", label: "Gemini 2.5 Flash (fast, good value)" }
67
+ ]
68
+ }
31
69
  ];
32
70
  function ask(rl, question) {
33
71
  return new Promise((resolve) => rl.question(question, resolve));
34
72
  }
35
- async function runSetup() {
36
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
37
- console.log();
38
- console.log(chalk.bold(" WorkerMill CLI") + chalk.dim(" \u2014 AI coding agent"));
39
- console.log();
40
- console.log(chalk.dim(" No provider configured. Let's set one up."));
41
- console.log();
42
- console.log(" Provider:");
43
- PROVIDERS.forEach((p, i) => {
73
+ async function pickProvider(rl, label, providers) {
74
+ console.log(` ${chalk.bold(label)}:`);
75
+ providers.forEach((p, i) => {
44
76
  console.log(` ${chalk.cyan(`${i + 1}`)}. ${p.display}`);
45
77
  });
46
78
  console.log();
47
- const choiceStr = await ask(rl, chalk.dim(" Choose (1-4): "));
79
+ const choiceStr = await ask(rl, chalk.dim(` Choose (1-${providers.length}): `));
48
80
  const choice = parseInt(choiceStr.trim(), 10) - 1;
49
- const selected = PROVIDERS[choice] || PROVIDERS[0];
81
+ const selected = providers[choice] || providers[0];
82
+ console.log(chalk.dim(` \u2192 ${selected.display}`));
83
+ return selected;
84
+ }
85
+ async function pickModel(rl, provider) {
50
86
  console.log();
51
- console.log(chalk.dim(` Selected: ${selected.display}`));
52
- const providerConfig = { model: selected.defaultModel };
53
- if (selected.needsKey) {
54
- const envValue = selected.envVar ? process.env[selected.envVar] : void 0;
55
- if (envValue) {
56
- console.log(chalk.green(` \u2713 Found ${selected.envVar} in environment`));
57
- providerConfig.apiKey = `{env:${selected.envVar}}`;
58
- } else {
59
- const key = await ask(rl, chalk.dim(` API key: `));
60
- providerConfig.apiKey = key.trim();
87
+ provider.models.forEach((m, i) => {
88
+ console.log(` ${chalk.cyan(`${i + 1}`)}. ${m.label}`);
89
+ });
90
+ console.log(` ${chalk.cyan(`${provider.models.length + 1}`)}. Custom model`);
91
+ console.log();
92
+ const choiceStr = await ask(rl, chalk.dim(` Choose (1-${provider.models.length + 1}): `));
93
+ const choice = parseInt(choiceStr.trim(), 10) - 1;
94
+ if (choice >= 0 && choice < provider.models.length) {
95
+ const model2 = provider.models[choice].id;
96
+ console.log(chalk.dim(` \u2192 ${model2}`));
97
+ return model2;
98
+ }
99
+ const custom = await ask(rl, chalk.dim(" Model name: "));
100
+ const model = custom.trim() || provider.models[0].id;
101
+ console.log(chalk.dim(` \u2192 ${model}`));
102
+ return model;
103
+ }
104
+ function maskKey(key) {
105
+ if (key.length <= 12) return "\u2022".repeat(key.length);
106
+ return key.slice(0, 6) + "\u2022".repeat(Math.min(key.length - 10, 30)) + key.slice(-4);
107
+ }
108
+ async function getApiKey(rl, provider, existingKeys) {
109
+ if (!provider.needsKey) return void 0;
110
+ if (existingKeys.has(provider.name)) {
111
+ console.log(chalk.green(` \u2713 Reusing ${provider.display} API key from earlier`));
112
+ return existingKeys.get(provider.name);
113
+ }
114
+ const envValue = provider.envVar ? process.env[provider.envVar] : void 0;
115
+ if (envValue) {
116
+ console.log(chalk.green(` \u2713 Found ${provider.envVar} in environment`));
117
+ const key2 = `{env:${provider.envVar}}`;
118
+ existingKeys.set(provider.name, key2);
119
+ return key2;
120
+ }
121
+ const key = await readKeyMasked(rl, chalk.dim(` ${provider.display} API key: `));
122
+ const trimmed = key.trim();
123
+ existingKeys.set(provider.name, trimmed);
124
+ return trimmed;
125
+ }
126
+ function readKeyMasked(rl, prompt) {
127
+ return new Promise((resolve) => {
128
+ let buffer = "";
129
+ let revealed = false;
130
+ process.stdout.write(prompt);
131
+ const wasRaw = process.stdin.isRaw;
132
+ if (process.stdin.isTTY) {
133
+ process.stdin.setRawMode(true);
61
134
  }
135
+ process.stdin.resume();
136
+ const redraw = () => {
137
+ process.stdout.write(`\r\x1B[K${prompt}`);
138
+ if (revealed) {
139
+ process.stdout.write(buffer);
140
+ } else {
141
+ process.stdout.write(maskKey(buffer));
142
+ }
143
+ };
144
+ const onData = (data) => {
145
+ const str = data.toString();
146
+ for (const ch of str) {
147
+ const code = ch.charCodeAt(0);
148
+ if (code === 13 || code === 10) {
149
+ process.stdin.removeListener("data", onData);
150
+ if (process.stdin.isTTY) {
151
+ process.stdin.setRawMode(wasRaw ?? false);
152
+ }
153
+ process.stdout.write(`\r\x1B[K${prompt}`);
154
+ if (buffer.length > 0) {
155
+ process.stdout.write(chalk.green(maskKey(buffer)));
156
+ }
157
+ process.stdout.write("\n");
158
+ resolve(buffer);
159
+ return;
160
+ }
161
+ if (code === 9) {
162
+ revealed = !revealed;
163
+ redraw();
164
+ continue;
165
+ }
166
+ if (code === 3) {
167
+ process.stdin.removeListener("data", onData);
168
+ if (process.stdin.isTTY) {
169
+ process.stdin.setRawMode(wasRaw ?? false);
170
+ }
171
+ process.stdout.write("\n");
172
+ resolve("");
173
+ return;
174
+ }
175
+ if (code === 127 || code === 8) {
176
+ if (buffer.length > 0) {
177
+ buffer = buffer.slice(0, -1);
178
+ redraw();
179
+ }
180
+ continue;
181
+ }
182
+ if (code === 21) {
183
+ buffer = "";
184
+ redraw();
185
+ continue;
186
+ }
187
+ if (code >= 32) {
188
+ buffer += ch;
189
+ redraw();
190
+ }
191
+ }
192
+ };
193
+ process.stdin.on("data", onData);
194
+ });
195
+ }
196
+ async function configureOllama(providerConfig) {
197
+ const hostsToTry = ["http://localhost:11434"];
198
+ try {
199
+ const gateway = execSync("ip route show default 2>/dev/null | awk '{print $3}'", { encoding: "utf-8" }).trim();
200
+ if (gateway) hostsToTry.push(`http://${gateway}:11434`);
201
+ } catch {
202
+ }
203
+ if (process.env.OLLAMA_HOST) {
204
+ const envHost = process.env.OLLAMA_HOST.startsWith("http") ? process.env.OLLAMA_HOST : `http://${process.env.OLLAMA_HOST}`;
205
+ hostsToTry.unshift(envHost);
62
206
  }
63
- if (selected.name === "ollama") {
64
- const hostsToTry = ["http://localhost:11434"];
207
+ providerConfig.contextLength = 65536;
208
+ for (const host of hostsToTry) {
65
209
  try {
66
- const gateway = execSync("ip route show default 2>/dev/null | awk '{print $3}'", { encoding: "utf-8" }).trim();
67
- if (gateway) {
68
- hostsToTry.push(`http://${gateway}:11434`);
69
- }
70
- } catch {
71
- }
72
- if (process.env.OLLAMA_HOST) {
73
- const envHost = process.env.OLLAMA_HOST.startsWith("http") ? process.env.OLLAMA_HOST : `http://${process.env.OLLAMA_HOST}`;
74
- hostsToTry.unshift(envHost);
75
- }
76
- let connectedHost = null;
77
- let models = [];
78
- for (const host of hostsToTry) {
79
- try {
80
- const controller = new AbortController();
81
- const timeout = setTimeout(() => controller.abort(), 3e3);
82
- const response = await globalThis.fetch(`${host}/api/tags`, { signal: controller.signal });
83
- clearTimeout(timeout);
84
- if (response.ok) {
85
- const data = await response.json();
86
- connectedHost = host;
87
- models = data.models || [];
88
- break;
210
+ const controller = new AbortController();
211
+ const timeout = setTimeout(() => controller.abort(), 3e3);
212
+ const response = await globalThis.fetch(`${host}/api/tags`, { signal: controller.signal });
213
+ clearTimeout(timeout);
214
+ if (response.ok) {
215
+ const data = await response.json();
216
+ providerConfig.host = host;
217
+ console.log(chalk.green(` \u2713 Connected to Ollama at ${host}`));
218
+ const models = data.models || [];
219
+ if (models.length > 0) {
220
+ console.log(chalk.dim(` Available: ${models.map((m) => m.name).join(", ")}`));
89
221
  }
90
- } catch {
91
- continue;
222
+ return;
92
223
  }
224
+ } catch {
225
+ continue;
93
226
  }
94
- providerConfig.contextLength = 65536;
95
- if (connectedHost) {
96
- providerConfig.host = connectedHost;
97
- console.log(chalk.green(` \u2713 Connected to Ollama at ${connectedHost}`));
98
- console.log(chalk.dim(` Context window: ${providerConfig.contextLength.toLocaleString()} tokens`));
99
- if (models.length > 0) {
100
- console.log(chalk.dim(` Available models: ${models.map((m) => m.name).join(", ")}`));
101
- }
102
- } else {
103
- providerConfig.host = "http://localhost:11434";
104
- console.log(chalk.yellow(" \u26A0 Could not connect to Ollama. Make sure it's running."));
105
- console.log(chalk.dim(" Tried: " + hostsToTry.join(", ")));
227
+ }
228
+ providerConfig.host = "http://localhost:11434";
229
+ console.log(chalk.yellow(" \u26A0 Could not connect to Ollama. Make sure it's running."));
230
+ }
231
+ async function runSetup() {
232
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
233
+ const apiKeys = /* @__PURE__ */ new Map();
234
+ console.log();
235
+ console.log(chalk.bold(" WorkerMill CLI") + chalk.dim(" \u2014 AI coding agent"));
236
+ console.log();
237
+ console.log(chalk.dim(" WorkerMill uses three roles. Each can use a different provider."));
238
+ console.log(chalk.dim(" Workers build code, the planner designs the plan, the reviewer checks quality."));
239
+ console.log();
240
+ console.log(chalk.hex("#D77757").bold(" \u2460 Workers") + chalk.dim(" \u2014 expert personas that write code"));
241
+ console.log();
242
+ const workerProvider = await pickProvider(rl, "Provider for workers", PROVIDERS);
243
+ const workerModel = await pickModel(rl, workerProvider);
244
+ const workerKey = await getApiKey(rl, workerProvider, apiKeys);
245
+ const workerConfig = { model: workerModel };
246
+ if (workerKey) workerConfig.apiKey = workerKey;
247
+ if (workerProvider.name === "ollama") await configureOllama(workerConfig);
248
+ console.log();
249
+ console.log(chalk.hex("#D77757").bold(" \u2461 Planner") + chalk.dim(" \u2014 plans stories and validates the approach"));
250
+ console.log();
251
+ const sameForPlanner = await ask(rl, chalk.dim(` Same as workers (${workerProvider.display} / ${workerModel})? [Y/n] `));
252
+ let plannerProviderName;
253
+ let plannerModel;
254
+ if (sameForPlanner.trim().toLowerCase() === "n") {
255
+ const plannerProvider = await pickProvider(rl, "Provider for planner", PROVIDERS);
256
+ plannerModel = await pickModel(rl, plannerProvider);
257
+ const plannerKey = await getApiKey(rl, plannerProvider, apiKeys);
258
+ plannerProviderName = plannerProvider.name;
259
+ if (plannerKey && plannerProvider.name !== workerProvider.name) {
106
260
  }
261
+ } else {
262
+ plannerProviderName = workerProvider.name;
263
+ plannerModel = workerModel;
264
+ console.log(chalk.dim(` \u2192 ${workerProvider.display} / ${workerModel}`));
107
265
  }
108
266
  console.log();
109
- const modelOverride = await ask(rl, chalk.dim(` Model (press Enter for ${selected.defaultModel}): `));
110
- if (modelOverride.trim()) {
111
- providerConfig.model = modelOverride.trim();
267
+ console.log(chalk.hex("#D77757").bold(" \u2462 Reviewer") + chalk.dim(" \u2014 tech lead that reviews code quality"));
268
+ console.log();
269
+ const sameForReviewer = await ask(rl, chalk.dim(` Same as workers (${workerProvider.display} / ${workerModel})? [Y/n] `));
270
+ let reviewerProviderName;
271
+ let reviewerModel;
272
+ if (sameForReviewer.trim().toLowerCase() === "n") {
273
+ const reviewerProvider = await pickProvider(rl, "Provider for reviewer", PROVIDERS);
274
+ reviewerModel = await pickModel(rl, reviewerProvider);
275
+ const reviewerKey = await getApiKey(rl, reviewerProvider, apiKeys);
276
+ reviewerProviderName = reviewerProvider.name;
277
+ if (reviewerKey && reviewerProvider.name !== workerProvider.name) {
278
+ }
279
+ } else {
280
+ reviewerProviderName = workerProvider.name;
281
+ reviewerModel = workerModel;
282
+ console.log(chalk.dim(` \u2192 ${workerProvider.display} / ${workerModel}`));
112
283
  }
113
284
  rl.close();
285
+ const providers = {
286
+ [workerProvider.name]: workerConfig
287
+ };
288
+ if (plannerProviderName !== workerProvider.name && !providers[plannerProviderName]) {
289
+ const pProvider = PROVIDERS.find((p) => p.name === plannerProviderName);
290
+ const cfg = { model: plannerModel };
291
+ const key = apiKeys.get(plannerProviderName);
292
+ if (key) cfg.apiKey = key;
293
+ if (pProvider.name === "ollama") await configureOllama(cfg);
294
+ providers[plannerProviderName] = cfg;
295
+ }
296
+ if (reviewerProviderName !== workerProvider.name && !providers[reviewerProviderName]) {
297
+ const rProvider = PROVIDERS.find((p) => p.name === reviewerProviderName);
298
+ const cfg = { model: reviewerModel };
299
+ const key = apiKeys.get(reviewerProviderName);
300
+ if (key) cfg.apiKey = key;
301
+ if (rProvider.name === "ollama") await configureOllama(cfg);
302
+ providers[reviewerProviderName] = cfg;
303
+ }
304
+ const routing = {};
305
+ if (plannerProviderName !== workerProvider.name) {
306
+ routing.planner = plannerProviderName;
307
+ routing.critic = plannerProviderName;
308
+ }
309
+ if (reviewerProviderName !== workerProvider.name) {
310
+ routing.tech_lead = reviewerProviderName;
311
+ }
312
+ if (plannerProviderName === workerProvider.name && plannerModel !== workerModel) {
313
+ const altKey = `${plannerProviderName}_planner`;
314
+ providers[altKey] = { ...providers[plannerProviderName], model: plannerModel };
315
+ routing.planner = altKey;
316
+ routing.critic = altKey;
317
+ }
318
+ if (reviewerProviderName === workerProvider.name && reviewerModel !== workerModel) {
319
+ const altKey = `${reviewerProviderName}_reviewer`;
320
+ providers[altKey] = { ...providers[reviewerProviderName], model: reviewerModel };
321
+ routing.tech_lead = altKey;
322
+ }
114
323
  const config = {
115
- providers: { [selected.name]: providerConfig },
116
- default: selected.name
324
+ providers,
325
+ default: workerProvider.name,
326
+ ...Object.keys(routing).length > 0 ? { routing } : {}
117
327
  };
118
328
  saveConfig(config);
119
329
  console.log();
120
330
  console.log(chalk.green(" \u2713 Config saved to ~/.workermill/cli.json"));
121
331
  console.log();
332
+ console.log(chalk.dim(" Workers: ") + `${workerProvider.name}/${workerModel}`);
333
+ console.log(chalk.dim(" Planner: ") + `${plannerProviderName}/${plannerModel}`);
334
+ console.log(chalk.dim(" Reviewer: ") + `${reviewerProviderName}/${reviewerModel}`);
335
+ console.log();
122
336
  return config;
123
337
  }
124
338
 
@@ -127,8 +341,8 @@ init_esm_shims();
127
341
  import { useState as useState5, useCallback as useCallback3, useRef as useRef3, useEffect as useEffect2 } from "react";
128
342
  import { useApp as useApp2 } from "ink";
129
343
  import { execSync as execSync2 } from "child_process";
130
- import fs3 from "fs";
131
- import path3 from "path";
344
+ import fs2 from "fs";
345
+ import path2 from "path";
132
346
  import os from "os";
133
347
 
134
348
  // src/ui/useAgent.ts
@@ -263,42 +477,6 @@ ${result.text}` },
263
477
  }
264
478
  }
265
479
 
266
- // src/logger.ts
267
- init_esm_shims();
268
- import fs2 from "fs";
269
- import path2 from "path";
270
- var LOG_DIR = path2.join(process.cwd(), ".workermill");
271
- var LOG_FILE = path2.join(LOG_DIR, "cli.log");
272
- var logStream = null;
273
- function ensureLogDir() {
274
- if (!fs2.existsSync(LOG_DIR)) {
275
- fs2.mkdirSync(LOG_DIR, { recursive: true });
276
- }
277
- }
278
- function getStream() {
279
- if (!logStream) {
280
- ensureLogDir();
281
- logStream = fs2.createWriteStream(LOG_FILE, { flags: "a" });
282
- }
283
- return logStream;
284
- }
285
- function timestamp() {
286
- return (/* @__PURE__ */ new Date()).toISOString();
287
- }
288
- function log(level, message, data) {
289
- const entry = data ? `[${timestamp()}] ${level}: ${message} ${JSON.stringify(data)}` : `[${timestamp()}] ${level}: ${message}`;
290
- getStream().write(entry + "\n");
291
- }
292
- function info(message, data) {
293
- log("INFO", message, data);
294
- }
295
- function error(message, data) {
296
- log("ERROR", message, data);
297
- }
298
- function debug(message, data) {
299
- log("DEBUG", message, data);
300
- }
301
-
302
480
  // src/ui/useAgent.ts
303
481
  var DANGEROUS_PATTERNS = [
304
482
  {
@@ -817,7 +995,7 @@ function useOrchestrator(addMessage2, setCost) {
817
995
  setRunning(false);
818
996
  return;
819
997
  }
820
- const { classifyComplexity, runOrchestration } = await import("./orchestrator-VRGIOUHT.js");
998
+ const { classifyComplexity, runOrchestration } = await import("./orchestrator-L5LDQYO7.js");
821
999
  const output = {
822
1000
  log(persona, message) {
823
1001
  const emoji = getEmoji(persona);
@@ -1574,13 +1752,13 @@ function App(props) {
1574
1752
 
1575
1753
  // src/ui/Root.tsx
1576
1754
  import { jsx as jsx7 } from "react/jsx-runtime";
1577
- var HISTORY_DIR = path3.join(os.homedir(), ".workermill");
1578
- var HISTORY_FILE = path3.join(HISTORY_DIR, "history");
1755
+ var HISTORY_DIR = path2.join(os.homedir(), ".workermill");
1756
+ var HISTORY_FILE = path2.join(HISTORY_DIR, "history");
1579
1757
  var MAX_HISTORY = 1e3;
1580
1758
  function loadHistory() {
1581
1759
  try {
1582
- if (fs3.existsSync(HISTORY_FILE)) {
1583
- const raw = fs3.readFileSync(HISTORY_FILE, "utf-8").trim();
1760
+ if (fs2.existsSync(HISTORY_FILE)) {
1761
+ const raw = fs2.readFileSync(HISTORY_FILE, "utf-8").trim();
1584
1762
  if (!raw) return [];
1585
1763
  return raw.split("\n").slice(-MAX_HISTORY);
1586
1764
  }
@@ -1590,10 +1768,10 @@ function loadHistory() {
1590
1768
  }
1591
1769
  function appendHistory(line) {
1592
1770
  try {
1593
- if (!fs3.existsSync(HISTORY_DIR)) {
1594
- fs3.mkdirSync(HISTORY_DIR, { recursive: true });
1771
+ if (!fs2.existsSync(HISTORY_DIR)) {
1772
+ fs2.mkdirSync(HISTORY_DIR, { recursive: true });
1595
1773
  }
1596
- fs3.appendFileSync(HISTORY_FILE, line + "\n", "utf-8");
1774
+ fs2.appendFileSync(HISTORY_FILE, line + "\n", "utf-8");
1597
1775
  } catch {
1598
1776
  }
1599
1777
  }
@@ -1835,15 +2013,15 @@ To change: edit \`~/.workermill/cli.json\` or restart with \`--provider\` / \`--
1835
2013
  // ---- /editor ----
1836
2014
  case "editor": {
1837
2015
  const editor = process.env.EDITOR || process.env.VISUAL || "vi";
1838
- const tmpFile = path3.join(os.tmpdir(), `workermill-${Date.now()}.md`);
2016
+ const tmpFile = path2.join(os.tmpdir(), `workermill-${Date.now()}.md`);
1839
2017
  try {
1840
- fs3.writeFileSync(tmpFile, "", "utf-8");
2018
+ fs2.writeFileSync(tmpFile, "", "utf-8");
1841
2019
  execSync2(`${editor} ${tmpFile}`, {
1842
2020
  cwd: props.workingDir,
1843
2021
  stdio: "inherit",
1844
2022
  timeout: 5 * 60 * 1e3
1845
2023
  });
1846
- const contents = fs3.readFileSync(tmpFile, "utf-8").trim();
2024
+ const contents = fs2.readFileSync(tmpFile, "utf-8").trim();
1847
2025
  if (contents) {
1848
2026
  agent.addUserMessage(contents);
1849
2027
  agent.submit(contents);
@@ -1855,7 +2033,7 @@ To change: edit \`~/.workermill/cli.json\` or restart with \`--provider\` / \`--
1855
2033
  agent.addSystemMessage(`Failed to open editor (\`${editor}\`): ${errMsg}`);
1856
2034
  } finally {
1857
2035
  try {
1858
- fs3.unlinkSync(tmpFile);
2036
+ fs2.unlinkSync(tmpFile);
1859
2037
  } catch {
1860
2038
  }
1861
2039
  }
@@ -1997,7 +2175,7 @@ function printWelcome(provider, model, workingDir) {
1997
2175
  console.log(dim(" Type ") + white("/help") + dim(" for all commands."));
1998
2176
  console.log();
1999
2177
  }
2000
- var VERSION = "0.3.3";
2178
+ var VERSION = "0.4.1";
2001
2179
  function addSharedOptions(cmd) {
2002
2180
  return cmd.option("--provider <provider>", "Override default provider").option("--model <model>", "Override model").option("--trust", "Skip all tool permission prompts").option("--full-disk", "Allow tools to access files outside working directory");
2003
2181
  }
@@ -4,9 +4,11 @@ import {
4
4
  buildOllamaOptions,
5
5
  createModel,
6
6
  createToolDefinitions,
7
+ error,
7
8
  getProviderForPersona,
9
+ info,
8
10
  init_esm_shims
9
- } from "./chunk-KL7SFKGG.js";
11
+ } from "./chunk-77CAVW3Y.js";
10
12
 
11
13
  // src/orchestrator.ts
12
14
  init_esm_shims();
@@ -153,9 +155,9 @@ function buildReasoningOptions(provider, modelName) {
153
155
  return {};
154
156
  }
155
157
  }
156
- function isTransientError(error) {
157
- if (!error || typeof error !== "object") return false;
158
- const msg = error instanceof Error ? error.message : String(error);
158
+ function isTransientError(error2) {
159
+ if (!error2 || typeof error2 !== "object") return false;
160
+ const msg = error2 instanceof Error ? error2.message : String(error2);
159
161
  if (/status code (502|503|504)|socket hang up|ECONNRESET|ETIMEDOUT|network error|ECONNREFUSED/i.test(msg)) {
160
162
  return true;
161
163
  }
@@ -219,6 +221,7 @@ function formatToolCallDisplay(toolName, toolInput) {
219
221
  return msg;
220
222
  }
221
223
  async function classifyComplexity(config, userInput, output) {
224
+ info("Classifying complexity", { input: userInput.slice(0, 200) });
222
225
  const resolvedInput = resolveTaskInput(userInput, process.cwd());
223
226
  const { provider, model: modelName, apiKey, host, contextLength } = getProviderForPersona(config);
224
227
  if (apiKey) {
@@ -635,6 +638,7 @@ ${plannerStories.map((s) => `- ${s.id}: ${s.title} (${s.persona}) \u2014 ${s.des
635
638
  output.coordinatorLog(`Task claimed by orchestrator`);
636
639
  output.log(story.persona, `Starting ${story.title}`);
637
640
  output.log(story.persona, `Executing story with AIClient (model: ${modelName})...`);
641
+ info(`Story ${i + 1}/${sorted.length} started`, { persona: story.persona, title: story.title, provider, model: modelName });
638
642
  output.status("");
639
643
  const model = createModel(provider, modelName, host, contextLength);
640
644
  const allTools = createToolDefinitions(workingDir, model, sandboxed);
@@ -765,13 +769,16 @@ ${revisionFeedback}` : ""}`;
765
769
  costTracker.addUsage(persona.name, provider, modelName, inTokens, outTokens);
766
770
  output.updateCost?.(costTracker.getTotalCost());
767
771
  output.log(story.persona, `${story.title} \u2014 completed! (${i + 1}/${sorted.length})`);
772
+ info(`Story ${i + 1} completed`, { persona: story.persona, inputTokens: inTokens, outputTokens: outTokens });
768
773
  output.log("system", "");
769
774
  break;
770
775
  } catch (err) {
771
776
  output.statusDone();
772
777
  const errMsg = err instanceof Error ? err.message : String(err);
778
+ error(`Story ${i + 1} error`, { persona: story.persona, error: errMsg, revision });
773
779
  if (isTransientError(err) && revision < 2) {
774
780
  output.log(story.persona, `Transient error: ${errMsg} \u2014 retrying...`);
781
+ info(`Story ${i + 1} retrying (transient)`, { revision });
775
782
  continue;
776
783
  }
777
784
  output.error(`Story ${i + 1} failed: ${errMsg}`);
@@ -812,8 +819,10 @@ ${revisionFeedback}` : ""}`;
812
819
  }
813
820
  }
814
821
  let previousReviewFeedback = "";
822
+ info("Starting review loop", { maxRevisions, approvalThreshold, provider: revProvider, model: revModel });
815
823
  for (let reviewRound = 0; reviewRound <= maxRevisions; reviewRound++) {
816
824
  const isRevision = reviewRound > 0;
825
+ info(`Review round ${reviewRound}`, { isRevision, maxRevisions });
817
826
  output.coordinatorLog(isRevision ? `Starting Tech Lead review (revision ${reviewRound}/${maxRevisions})...` : "Starting Tech Lead review...");
818
827
  output.log("tech_lead", `Starting agent execution (model: ${revModel})`);
819
828
  output.status(isRevision ? "Reviewer -- Re-checking after revisions" : "Reviewer -- Checking code quality");
@@ -907,6 +916,7 @@ AFFECTED_REASONS: {"2": "Missing error handling in auth controller", "3": "Front
907
916
  output.statusDone();
908
917
  const score = extractScore(reviewText);
909
918
  const approved = score >= approvalThreshold;
919
+ info(`Review round ${reviewRound} result`, { score, approved, threshold: approvalThreshold, reviewTextLength: reviewText.length });
910
920
  output.log("tech_lead", `::code_quality_score::${score}`);
911
921
  output.log("tech_lead", `::review_decision::${approved ? "approved" : "needs_revision"}`);
912
922
  output.coordinatorLog(approved ? `Review approved (score: ${score}/100)` : `Review needs revision (score: ${score}/100)`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workermill",
3
- "version": "0.3.3",
3
+ "version": "0.4.1",
4
4
  "description": "AI coding agent with multi-expert orchestration. Works with any LLM provider.",
5
5
  "type": "module",
6
6
  "bin": {