workermill 0.4.0 → 0.4.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.
@@ -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();
@@ -49,8 +52,8 @@ var PROVIDERS = [
49
52
  needsKey: true,
50
53
  envVar: "OPENAI_API_KEY",
51
54
  models: [
52
- { id: "gpt-5.2-codex", label: "GPT-5.2 Codex (built for code)" },
53
- { id: "gpt-5", label: "GPT-5 (general flagship)" }
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)" }
54
57
  ]
55
58
  },
56
59
  {
@@ -115,12 +118,14 @@ async function getApiKey(rl, provider, existingKeys) {
115
118
  existingKeys.set(provider.name, key2);
116
119
  return key2;
117
120
  }
118
- const key = await readKeyMasked(rl, chalk.dim(` ${provider.display} API key: `));
121
+ rl.pause();
122
+ const key = await readKeyMasked(chalk.dim(` ${provider.display} API key: `));
123
+ rl.resume();
119
124
  const trimmed = key.trim();
120
125
  existingKeys.set(provider.name, trimmed);
121
126
  return trimmed;
122
127
  }
123
- function readKeyMasked(rl, prompt) {
128
+ function readKeyMasked(prompt) {
124
129
  return new Promise((resolve) => {
125
130
  let buffer = "";
126
131
  let revealed = false;
@@ -338,8 +343,8 @@ init_esm_shims();
338
343
  import { useState as useState5, useCallback as useCallback3, useRef as useRef3, useEffect as useEffect2 } from "react";
339
344
  import { useApp as useApp2 } from "ink";
340
345
  import { execSync as execSync2 } from "child_process";
341
- import fs3 from "fs";
342
- import path3 from "path";
346
+ import fs2 from "fs";
347
+ import path2 from "path";
343
348
  import os from "os";
344
349
 
345
350
  // src/ui/useAgent.ts
@@ -474,42 +479,6 @@ ${result.text}` },
474
479
  }
475
480
  }
476
481
 
477
- // src/logger.ts
478
- init_esm_shims();
479
- import fs2 from "fs";
480
- import path2 from "path";
481
- var LOG_DIR = path2.join(process.cwd(), ".workermill");
482
- var LOG_FILE = path2.join(LOG_DIR, "cli.log");
483
- var logStream = null;
484
- function ensureLogDir() {
485
- if (!fs2.existsSync(LOG_DIR)) {
486
- fs2.mkdirSync(LOG_DIR, { recursive: true });
487
- }
488
- }
489
- function getStream() {
490
- if (!logStream) {
491
- ensureLogDir();
492
- logStream = fs2.createWriteStream(LOG_FILE, { flags: "a" });
493
- }
494
- return logStream;
495
- }
496
- function timestamp() {
497
- return (/* @__PURE__ */ new Date()).toISOString();
498
- }
499
- function log(level, message, data) {
500
- const entry = data ? `[${timestamp()}] ${level}: ${message} ${JSON.stringify(data)}` : `[${timestamp()}] ${level}: ${message}`;
501
- getStream().write(entry + "\n");
502
- }
503
- function info(message, data) {
504
- log("INFO", message, data);
505
- }
506
- function error(message, data) {
507
- log("ERROR", message, data);
508
- }
509
- function debug(message, data) {
510
- log("DEBUG", message, data);
511
- }
512
-
513
482
  // src/ui/useAgent.ts
514
483
  var DANGEROUS_PATTERNS = [
515
484
  {
@@ -1028,7 +997,7 @@ function useOrchestrator(addMessage2, setCost) {
1028
997
  setRunning(false);
1029
998
  return;
1030
999
  }
1031
- const { classifyComplexity, runOrchestration } = await import("./orchestrator-VRGIOUHT.js");
1000
+ const { classifyComplexity, runOrchestration } = await import("./orchestrator-L5LDQYO7.js");
1032
1001
  const output = {
1033
1002
  log(persona, message) {
1034
1003
  const emoji = getEmoji(persona);
@@ -1608,7 +1577,13 @@ function StatusBar(props) {
1608
1577
  break;
1609
1578
  }
1610
1579
  const bgColor = theme.subtleDark;
1611
- const modelStr = ` ${props.provider}/${props.model} `;
1580
+ const rm = props.roleModels;
1581
+ const workerStr = rm?.worker || `${props.provider}/${props.model}`;
1582
+ const extraRoles = [];
1583
+ if (rm && rm.planner !== rm.worker) extraRoles.push(`plan:${rm.planner}`);
1584
+ if (rm && rm.reviewer !== rm.worker) extraRoles.push(`review:${rm.reviewer}`);
1585
+ const rolesStr = extraRoles.length > 0 ? ` (${extraRoles.join(", ")})` : "";
1586
+ const modelStr = ` ${workerStr}${rolesStr} `;
1612
1587
  const pct = props.maxContext > 0 ? Math.round(usage * 100) : 0;
1613
1588
  const tokenStr = ` ${pct}% `;
1614
1589
  const costStr = formatCost(props.cost);
@@ -1768,7 +1743,8 @@ function App(props) {
1768
1743
  cost: props.cost,
1769
1744
  mode,
1770
1745
  gitBranch: props.gitBranch,
1771
- cwd: props.workingDir.split("/").pop() || ""
1746
+ cwd: props.workingDir.split("/").pop() || "",
1747
+ roleModels: props.roleModels
1772
1748
  }
1773
1749
  ),
1774
1750
  /* @__PURE__ */ jsx6(
@@ -1785,13 +1761,13 @@ function App(props) {
1785
1761
 
1786
1762
  // src/ui/Root.tsx
1787
1763
  import { jsx as jsx7 } from "react/jsx-runtime";
1788
- var HISTORY_DIR = path3.join(os.homedir(), ".workermill");
1789
- var HISTORY_FILE = path3.join(HISTORY_DIR, "history");
1764
+ var HISTORY_DIR = path2.join(os.homedir(), ".workermill");
1765
+ var HISTORY_FILE = path2.join(HISTORY_DIR, "history");
1790
1766
  var MAX_HISTORY = 1e3;
1791
1767
  function loadHistory() {
1792
1768
  try {
1793
- if (fs3.existsSync(HISTORY_FILE)) {
1794
- const raw = fs3.readFileSync(HISTORY_FILE, "utf-8").trim();
1769
+ if (fs2.existsSync(HISTORY_FILE)) {
1770
+ const raw = fs2.readFileSync(HISTORY_FILE, "utf-8").trim();
1795
1771
  if (!raw) return [];
1796
1772
  return raw.split("\n").slice(-MAX_HISTORY);
1797
1773
  }
@@ -1801,10 +1777,10 @@ function loadHistory() {
1801
1777
  }
1802
1778
  function appendHistory(line) {
1803
1779
  try {
1804
- if (!fs3.existsSync(HISTORY_DIR)) {
1805
- fs3.mkdirSync(HISTORY_DIR, { recursive: true });
1780
+ if (!fs2.existsSync(HISTORY_DIR)) {
1781
+ fs2.mkdirSync(HISTORY_DIR, { recursive: true });
1806
1782
  }
1807
- fs3.appendFileSync(HISTORY_FILE, line + "\n", "utf-8");
1783
+ fs2.appendFileSync(HISTORY_FILE, line + "\n", "utf-8");
1808
1784
  } catch {
1809
1785
  }
1810
1786
  }
@@ -2046,15 +2022,15 @@ To change: edit \`~/.workermill/cli.json\` or restart with \`--provider\` / \`--
2046
2022
  // ---- /editor ----
2047
2023
  case "editor": {
2048
2024
  const editor = process.env.EDITOR || process.env.VISUAL || "vi";
2049
- const tmpFile = path3.join(os.tmpdir(), `workermill-${Date.now()}.md`);
2025
+ const tmpFile = path2.join(os.tmpdir(), `workermill-${Date.now()}.md`);
2050
2026
  try {
2051
- fs3.writeFileSync(tmpFile, "", "utf-8");
2027
+ fs2.writeFileSync(tmpFile, "", "utf-8");
2052
2028
  execSync2(`${editor} ${tmpFile}`, {
2053
2029
  cwd: props.workingDir,
2054
2030
  stdio: "inherit",
2055
2031
  timeout: 5 * 60 * 1e3
2056
2032
  });
2057
- const contents = fs3.readFileSync(tmpFile, "utf-8").trim();
2033
+ const contents = fs2.readFileSync(tmpFile, "utf-8").trim();
2058
2034
  if (contents) {
2059
2035
  agent.addUserMessage(contents);
2060
2036
  agent.submit(contents);
@@ -2066,7 +2042,7 @@ To change: edit \`~/.workermill/cli.json\` or restart with \`--provider\` / \`--
2066
2042
  agent.addSystemMessage(`Failed to open editor (\`${editor}\`): ${errMsg}`);
2067
2043
  } finally {
2068
2044
  try {
2069
- fs3.unlinkSync(tmpFile);
2045
+ fs2.unlinkSync(tmpFile);
2070
2046
  } catch {
2071
2047
  }
2072
2048
  }
@@ -2188,27 +2164,44 @@ ${trimmedOutput}
2188
2164
  tokens: agent.tokens,
2189
2165
  cost: agent.cost,
2190
2166
  gitBranch,
2191
- inputHistory
2167
+ inputHistory,
2168
+ roleModels: props.roleModels
2192
2169
  }
2193
2170
  );
2194
2171
  }
2195
2172
 
2196
2173
  // src/index.ts
2197
- function printWelcome(provider, model, workingDir) {
2174
+ function getRoleModelsFromConfig(config) {
2175
+ const worker = getProviderForPersona(config);
2176
+ const planner = getProviderForPersona(config, "planner");
2177
+ const reviewer = getProviderForPersona(config, "tech_lead");
2178
+ return {
2179
+ worker: `${worker.provider}/${worker.model}`,
2180
+ planner: `${planner.provider}/${planner.model}`,
2181
+ reviewer: `${reviewer.provider}/${reviewer.model}`
2182
+ };
2183
+ }
2184
+ function printWelcome(roleModels, workingDir) {
2198
2185
  const brand = chalk2.hex("#D77757");
2199
2186
  const dim = chalk2.dim;
2200
2187
  const white = chalk2.white;
2201
2188
  console.log();
2202
2189
  console.log(` ${brand("\u25C6")} ${white.bold("WorkerMill")} ${dim("v" + VERSION)}`);
2203
2190
  console.log();
2204
- console.log(dim(` ${provider}/${model}`));
2191
+ if (roleModels.planner === roleModels.worker && roleModels.reviewer === roleModels.worker) {
2192
+ console.log(dim(` ${roleModels.worker}`));
2193
+ } else {
2194
+ console.log(dim(` worker: ${roleModels.worker}`));
2195
+ console.log(dim(` planner: ${roleModels.planner}`));
2196
+ console.log(dim(` reviewer: ${roleModels.reviewer}`));
2197
+ }
2205
2198
  console.log(dim(` cwd: ${workingDir}`));
2206
2199
  console.log();
2207
2200
  console.log(dim(" Ask me anything, or use ") + brand("/build") + dim(" to create software with multi-expert AI."));
2208
2201
  console.log(dim(" Type ") + white("/help") + dim(" for all commands."));
2209
2202
  console.log();
2210
2203
  }
2211
- var VERSION = "0.4.0";
2204
+ var VERSION = "0.4.2";
2212
2205
  function addSharedOptions(cmd) {
2213
2206
  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");
2214
2207
  }
@@ -2233,7 +2226,8 @@ var defaultCmd = program.command("chat", { isDefault: true }).description("Inter
2233
2226
  const config = await resolveConfig(options);
2234
2227
  const { provider, model, apiKey, host, contextLength } = getProviderForPersona(config);
2235
2228
  const workingDir = process.cwd();
2236
- printWelcome(provider, model, workingDir);
2229
+ const roleModels = getRoleModelsFromConfig(config);
2230
+ printWelcome(roleModels, workingDir);
2237
2231
  const { waitUntilExit } = render(
2238
2232
  React5.createElement(Root, {
2239
2233
  provider,
@@ -2245,7 +2239,8 @@ var defaultCmd = program.command("chat", { isDefault: true }).description("Inter
2245
2239
  planMode: options.plan || false,
2246
2240
  sandboxed: !options.fullDisk,
2247
2241
  resume: options.resume || false,
2248
- workingDir
2242
+ workingDir,
2243
+ roleModels
2249
2244
  })
2250
2245
  );
2251
2246
  await waitUntilExit();
@@ -2265,6 +2260,7 @@ var buildCmd = program.command("build [task...]").description("Build software wi
2265
2260
  config.review = { ...config.review, useCritic: true };
2266
2261
  }
2267
2262
  const { provider, model, apiKey, host, contextLength } = getProviderForPersona(config);
2263
+ const roleModels = getRoleModelsFromConfig(config);
2268
2264
  const trustAll = options.trust || false;
2269
2265
  const sandboxed = !options.fullDisk;
2270
2266
  if (apiKey) {
@@ -2290,7 +2286,8 @@ var buildCmd = program.command("build [task...]").description("Build software wi
2290
2286
  sandboxed,
2291
2287
  resume: false,
2292
2288
  workingDir: process.cwd(),
2293
- initialBuildTask: task
2289
+ initialBuildTask: task,
2290
+ roleModels
2294
2291
  })
2295
2292
  );
2296
2293
  await waitUntilExit();
@@ -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.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "AI coding agent with multi-expert orchestration. Works with any LLM provider.",
5
5
  "type": "module",
6
6
  "bin": {