workermill 0.3.2 → 0.4.0
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/index.js
CHANGED
|
@@ -24,101 +24,312 @@ import readline from "readline";
|
|
|
24
24
|
import { execSync } from "child_process";
|
|
25
25
|
import chalk from "chalk";
|
|
26
26
|
var PROVIDERS = [
|
|
27
|
-
{
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
{
|
|
28
|
+
name: "ollama",
|
|
29
|
+
display: "Ollama (local, no API key)",
|
|
30
|
+
needsKey: false,
|
|
31
|
+
models: [
|
|
32
|
+
{ id: "qwen3-coder:30b", label: "Qwen 3 Coder 30B (recommended)" },
|
|
33
|
+
{ id: "qwen2.5-coder:32b", label: "Qwen 2.5 Coder 32B" }
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "anthropic",
|
|
38
|
+
display: "Anthropic (Claude)",
|
|
39
|
+
needsKey: true,
|
|
40
|
+
envVar: "ANTHROPIC_API_KEY",
|
|
41
|
+
models: [
|
|
42
|
+
{ id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6 (recommended)" },
|
|
43
|
+
{ id: "claude-opus-4-6", label: "Claude Opus 4.6 (most powerful)" }
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "openai",
|
|
48
|
+
display: "OpenAI",
|
|
49
|
+
needsKey: true,
|
|
50
|
+
envVar: "OPENAI_API_KEY",
|
|
51
|
+
models: [
|
|
52
|
+
{ id: "gpt-5.2-codex", label: "GPT-5.2 Codex (built for code)" },
|
|
53
|
+
{ id: "gpt-5", label: "GPT-5 (general flagship)" }
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "google",
|
|
58
|
+
display: "Google (Gemini)",
|
|
59
|
+
needsKey: true,
|
|
60
|
+
envVar: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
61
|
+
models: [
|
|
62
|
+
{ id: "gemini-2.5-pro", label: "Gemini 2.5 Pro (most powerful)" },
|
|
63
|
+
{ id: "gemini-2.5-flash", label: "Gemini 2.5 Flash (fast, good value)" }
|
|
64
|
+
]
|
|
65
|
+
}
|
|
31
66
|
];
|
|
32
67
|
function ask(rl, question) {
|
|
33
68
|
return new Promise((resolve) => rl.question(question, resolve));
|
|
34
69
|
}
|
|
35
|
-
async function
|
|
36
|
-
|
|
37
|
-
|
|
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) => {
|
|
70
|
+
async function pickProvider(rl, label, providers) {
|
|
71
|
+
console.log(` ${chalk.bold(label)}:`);
|
|
72
|
+
providers.forEach((p, i) => {
|
|
44
73
|
console.log(` ${chalk.cyan(`${i + 1}`)}. ${p.display}`);
|
|
45
74
|
});
|
|
46
75
|
console.log();
|
|
47
|
-
const choiceStr = await ask(rl, chalk.dim(
|
|
76
|
+
const choiceStr = await ask(rl, chalk.dim(` Choose (1-${providers.length}): `));
|
|
48
77
|
const choice = parseInt(choiceStr.trim(), 10) - 1;
|
|
49
|
-
const selected =
|
|
78
|
+
const selected = providers[choice] || providers[0];
|
|
79
|
+
console.log(chalk.dim(` \u2192 ${selected.display}`));
|
|
80
|
+
return selected;
|
|
81
|
+
}
|
|
82
|
+
async function pickModel(rl, provider) {
|
|
50
83
|
console.log();
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
84
|
+
provider.models.forEach((m, i) => {
|
|
85
|
+
console.log(` ${chalk.cyan(`${i + 1}`)}. ${m.label}`);
|
|
86
|
+
});
|
|
87
|
+
console.log(` ${chalk.cyan(`${provider.models.length + 1}`)}. Custom model`);
|
|
88
|
+
console.log();
|
|
89
|
+
const choiceStr = await ask(rl, chalk.dim(` Choose (1-${provider.models.length + 1}): `));
|
|
90
|
+
const choice = parseInt(choiceStr.trim(), 10) - 1;
|
|
91
|
+
if (choice >= 0 && choice < provider.models.length) {
|
|
92
|
+
const model2 = provider.models[choice].id;
|
|
93
|
+
console.log(chalk.dim(` \u2192 ${model2}`));
|
|
94
|
+
return model2;
|
|
95
|
+
}
|
|
96
|
+
const custom = await ask(rl, chalk.dim(" Model name: "));
|
|
97
|
+
const model = custom.trim() || provider.models[0].id;
|
|
98
|
+
console.log(chalk.dim(` \u2192 ${model}`));
|
|
99
|
+
return model;
|
|
100
|
+
}
|
|
101
|
+
function maskKey(key) {
|
|
102
|
+
if (key.length <= 12) return "\u2022".repeat(key.length);
|
|
103
|
+
return key.slice(0, 6) + "\u2022".repeat(Math.min(key.length - 10, 30)) + key.slice(-4);
|
|
104
|
+
}
|
|
105
|
+
async function getApiKey(rl, provider, existingKeys) {
|
|
106
|
+
if (!provider.needsKey) return void 0;
|
|
107
|
+
if (existingKeys.has(provider.name)) {
|
|
108
|
+
console.log(chalk.green(` \u2713 Reusing ${provider.display} API key from earlier`));
|
|
109
|
+
return existingKeys.get(provider.name);
|
|
110
|
+
}
|
|
111
|
+
const envValue = provider.envVar ? process.env[provider.envVar] : void 0;
|
|
112
|
+
if (envValue) {
|
|
113
|
+
console.log(chalk.green(` \u2713 Found ${provider.envVar} in environment`));
|
|
114
|
+
const key2 = `{env:${provider.envVar}}`;
|
|
115
|
+
existingKeys.set(provider.name, key2);
|
|
116
|
+
return key2;
|
|
117
|
+
}
|
|
118
|
+
const key = await readKeyMasked(rl, chalk.dim(` ${provider.display} API key: `));
|
|
119
|
+
const trimmed = key.trim();
|
|
120
|
+
existingKeys.set(provider.name, trimmed);
|
|
121
|
+
return trimmed;
|
|
122
|
+
}
|
|
123
|
+
function readKeyMasked(rl, prompt) {
|
|
124
|
+
return new Promise((resolve) => {
|
|
125
|
+
let buffer = "";
|
|
126
|
+
let revealed = false;
|
|
127
|
+
process.stdout.write(prompt);
|
|
128
|
+
const wasRaw = process.stdin.isRaw;
|
|
129
|
+
if (process.stdin.isTTY) {
|
|
130
|
+
process.stdin.setRawMode(true);
|
|
61
131
|
}
|
|
132
|
+
process.stdin.resume();
|
|
133
|
+
const redraw = () => {
|
|
134
|
+
process.stdout.write(`\r\x1B[K${prompt}`);
|
|
135
|
+
if (revealed) {
|
|
136
|
+
process.stdout.write(buffer);
|
|
137
|
+
} else {
|
|
138
|
+
process.stdout.write(maskKey(buffer));
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const onData = (data) => {
|
|
142
|
+
const str = data.toString();
|
|
143
|
+
for (const ch of str) {
|
|
144
|
+
const code = ch.charCodeAt(0);
|
|
145
|
+
if (code === 13 || code === 10) {
|
|
146
|
+
process.stdin.removeListener("data", onData);
|
|
147
|
+
if (process.stdin.isTTY) {
|
|
148
|
+
process.stdin.setRawMode(wasRaw ?? false);
|
|
149
|
+
}
|
|
150
|
+
process.stdout.write(`\r\x1B[K${prompt}`);
|
|
151
|
+
if (buffer.length > 0) {
|
|
152
|
+
process.stdout.write(chalk.green(maskKey(buffer)));
|
|
153
|
+
}
|
|
154
|
+
process.stdout.write("\n");
|
|
155
|
+
resolve(buffer);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (code === 9) {
|
|
159
|
+
revealed = !revealed;
|
|
160
|
+
redraw();
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (code === 3) {
|
|
164
|
+
process.stdin.removeListener("data", onData);
|
|
165
|
+
if (process.stdin.isTTY) {
|
|
166
|
+
process.stdin.setRawMode(wasRaw ?? false);
|
|
167
|
+
}
|
|
168
|
+
process.stdout.write("\n");
|
|
169
|
+
resolve("");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (code === 127 || code === 8) {
|
|
173
|
+
if (buffer.length > 0) {
|
|
174
|
+
buffer = buffer.slice(0, -1);
|
|
175
|
+
redraw();
|
|
176
|
+
}
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (code === 21) {
|
|
180
|
+
buffer = "";
|
|
181
|
+
redraw();
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (code >= 32) {
|
|
185
|
+
buffer += ch;
|
|
186
|
+
redraw();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
process.stdin.on("data", onData);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
async function configureOllama(providerConfig) {
|
|
194
|
+
const hostsToTry = ["http://localhost:11434"];
|
|
195
|
+
try {
|
|
196
|
+
const gateway = execSync("ip route show default 2>/dev/null | awk '{print $3}'", { encoding: "utf-8" }).trim();
|
|
197
|
+
if (gateway) hostsToTry.push(`http://${gateway}:11434`);
|
|
198
|
+
} catch {
|
|
199
|
+
}
|
|
200
|
+
if (process.env.OLLAMA_HOST) {
|
|
201
|
+
const envHost = process.env.OLLAMA_HOST.startsWith("http") ? process.env.OLLAMA_HOST : `http://${process.env.OLLAMA_HOST}`;
|
|
202
|
+
hostsToTry.unshift(envHost);
|
|
62
203
|
}
|
|
63
|
-
|
|
64
|
-
|
|
204
|
+
providerConfig.contextLength = 65536;
|
|
205
|
+
for (const host of hostsToTry) {
|
|
65
206
|
try {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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;
|
|
207
|
+
const controller = new AbortController();
|
|
208
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
209
|
+
const response = await globalThis.fetch(`${host}/api/tags`, { signal: controller.signal });
|
|
210
|
+
clearTimeout(timeout);
|
|
211
|
+
if (response.ok) {
|
|
212
|
+
const data = await response.json();
|
|
213
|
+
providerConfig.host = host;
|
|
214
|
+
console.log(chalk.green(` \u2713 Connected to Ollama at ${host}`));
|
|
215
|
+
const models = data.models || [];
|
|
216
|
+
if (models.length > 0) {
|
|
217
|
+
console.log(chalk.dim(` Available: ${models.map((m) => m.name).join(", ")}`));
|
|
89
218
|
}
|
|
90
|
-
|
|
91
|
-
continue;
|
|
219
|
+
return;
|
|
92
220
|
}
|
|
221
|
+
} catch {
|
|
222
|
+
continue;
|
|
93
223
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
224
|
+
}
|
|
225
|
+
providerConfig.host = "http://localhost:11434";
|
|
226
|
+
console.log(chalk.yellow(" \u26A0 Could not connect to Ollama. Make sure it's running."));
|
|
227
|
+
}
|
|
228
|
+
async function runSetup() {
|
|
229
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
230
|
+
const apiKeys = /* @__PURE__ */ new Map();
|
|
231
|
+
console.log();
|
|
232
|
+
console.log(chalk.bold(" WorkerMill CLI") + chalk.dim(" \u2014 AI coding agent"));
|
|
233
|
+
console.log();
|
|
234
|
+
console.log(chalk.dim(" WorkerMill uses three roles. Each can use a different provider."));
|
|
235
|
+
console.log(chalk.dim(" Workers build code, the planner designs the plan, the reviewer checks quality."));
|
|
236
|
+
console.log();
|
|
237
|
+
console.log(chalk.hex("#D77757").bold(" \u2460 Workers") + chalk.dim(" \u2014 expert personas that write code"));
|
|
238
|
+
console.log();
|
|
239
|
+
const workerProvider = await pickProvider(rl, "Provider for workers", PROVIDERS);
|
|
240
|
+
const workerModel = await pickModel(rl, workerProvider);
|
|
241
|
+
const workerKey = await getApiKey(rl, workerProvider, apiKeys);
|
|
242
|
+
const workerConfig = { model: workerModel };
|
|
243
|
+
if (workerKey) workerConfig.apiKey = workerKey;
|
|
244
|
+
if (workerProvider.name === "ollama") await configureOllama(workerConfig);
|
|
245
|
+
console.log();
|
|
246
|
+
console.log(chalk.hex("#D77757").bold(" \u2461 Planner") + chalk.dim(" \u2014 plans stories and validates the approach"));
|
|
247
|
+
console.log();
|
|
248
|
+
const sameForPlanner = await ask(rl, chalk.dim(` Same as workers (${workerProvider.display} / ${workerModel})? [Y/n] `));
|
|
249
|
+
let plannerProviderName;
|
|
250
|
+
let plannerModel;
|
|
251
|
+
if (sameForPlanner.trim().toLowerCase() === "n") {
|
|
252
|
+
const plannerProvider = await pickProvider(rl, "Provider for planner", PROVIDERS);
|
|
253
|
+
plannerModel = await pickModel(rl, plannerProvider);
|
|
254
|
+
const plannerKey = await getApiKey(rl, plannerProvider, apiKeys);
|
|
255
|
+
plannerProviderName = plannerProvider.name;
|
|
256
|
+
if (plannerKey && plannerProvider.name !== workerProvider.name) {
|
|
106
257
|
}
|
|
258
|
+
} else {
|
|
259
|
+
plannerProviderName = workerProvider.name;
|
|
260
|
+
plannerModel = workerModel;
|
|
261
|
+
console.log(chalk.dim(` \u2192 ${workerProvider.display} / ${workerModel}`));
|
|
107
262
|
}
|
|
108
263
|
console.log();
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
264
|
+
console.log(chalk.hex("#D77757").bold(" \u2462 Reviewer") + chalk.dim(" \u2014 tech lead that reviews code quality"));
|
|
265
|
+
console.log();
|
|
266
|
+
const sameForReviewer = await ask(rl, chalk.dim(` Same as workers (${workerProvider.display} / ${workerModel})? [Y/n] `));
|
|
267
|
+
let reviewerProviderName;
|
|
268
|
+
let reviewerModel;
|
|
269
|
+
if (sameForReviewer.trim().toLowerCase() === "n") {
|
|
270
|
+
const reviewerProvider = await pickProvider(rl, "Provider for reviewer", PROVIDERS);
|
|
271
|
+
reviewerModel = await pickModel(rl, reviewerProvider);
|
|
272
|
+
const reviewerKey = await getApiKey(rl, reviewerProvider, apiKeys);
|
|
273
|
+
reviewerProviderName = reviewerProvider.name;
|
|
274
|
+
if (reviewerKey && reviewerProvider.name !== workerProvider.name) {
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
reviewerProviderName = workerProvider.name;
|
|
278
|
+
reviewerModel = workerModel;
|
|
279
|
+
console.log(chalk.dim(` \u2192 ${workerProvider.display} / ${workerModel}`));
|
|
112
280
|
}
|
|
113
281
|
rl.close();
|
|
282
|
+
const providers = {
|
|
283
|
+
[workerProvider.name]: workerConfig
|
|
284
|
+
};
|
|
285
|
+
if (plannerProviderName !== workerProvider.name && !providers[plannerProviderName]) {
|
|
286
|
+
const pProvider = PROVIDERS.find((p) => p.name === plannerProviderName);
|
|
287
|
+
const cfg = { model: plannerModel };
|
|
288
|
+
const key = apiKeys.get(plannerProviderName);
|
|
289
|
+
if (key) cfg.apiKey = key;
|
|
290
|
+
if (pProvider.name === "ollama") await configureOllama(cfg);
|
|
291
|
+
providers[plannerProviderName] = cfg;
|
|
292
|
+
}
|
|
293
|
+
if (reviewerProviderName !== workerProvider.name && !providers[reviewerProviderName]) {
|
|
294
|
+
const rProvider = PROVIDERS.find((p) => p.name === reviewerProviderName);
|
|
295
|
+
const cfg = { model: reviewerModel };
|
|
296
|
+
const key = apiKeys.get(reviewerProviderName);
|
|
297
|
+
if (key) cfg.apiKey = key;
|
|
298
|
+
if (rProvider.name === "ollama") await configureOllama(cfg);
|
|
299
|
+
providers[reviewerProviderName] = cfg;
|
|
300
|
+
}
|
|
301
|
+
const routing = {};
|
|
302
|
+
if (plannerProviderName !== workerProvider.name) {
|
|
303
|
+
routing.planner = plannerProviderName;
|
|
304
|
+
routing.critic = plannerProviderName;
|
|
305
|
+
}
|
|
306
|
+
if (reviewerProviderName !== workerProvider.name) {
|
|
307
|
+
routing.tech_lead = reviewerProviderName;
|
|
308
|
+
}
|
|
309
|
+
if (plannerProviderName === workerProvider.name && plannerModel !== workerModel) {
|
|
310
|
+
const altKey = `${plannerProviderName}_planner`;
|
|
311
|
+
providers[altKey] = { ...providers[plannerProviderName], model: plannerModel };
|
|
312
|
+
routing.planner = altKey;
|
|
313
|
+
routing.critic = altKey;
|
|
314
|
+
}
|
|
315
|
+
if (reviewerProviderName === workerProvider.name && reviewerModel !== workerModel) {
|
|
316
|
+
const altKey = `${reviewerProviderName}_reviewer`;
|
|
317
|
+
providers[altKey] = { ...providers[reviewerProviderName], model: reviewerModel };
|
|
318
|
+
routing.tech_lead = altKey;
|
|
319
|
+
}
|
|
114
320
|
const config = {
|
|
115
|
-
providers
|
|
116
|
-
default:
|
|
321
|
+
providers,
|
|
322
|
+
default: workerProvider.name,
|
|
323
|
+
...Object.keys(routing).length > 0 ? { routing } : {}
|
|
117
324
|
};
|
|
118
325
|
saveConfig(config);
|
|
119
326
|
console.log();
|
|
120
327
|
console.log(chalk.green(" \u2713 Config saved to ~/.workermill/cli.json"));
|
|
121
328
|
console.log();
|
|
329
|
+
console.log(chalk.dim(" Workers: ") + `${workerProvider.name}/${workerModel}`);
|
|
330
|
+
console.log(chalk.dim(" Planner: ") + `${plannerProviderName}/${plannerModel}`);
|
|
331
|
+
console.log(chalk.dim(" Reviewer: ") + `${reviewerProviderName}/${reviewerModel}`);
|
|
332
|
+
console.log();
|
|
122
333
|
return config;
|
|
123
334
|
}
|
|
124
335
|
|
|
@@ -743,7 +954,8 @@ function useAgent(options) {
|
|
|
743
954
|
setTrustAll,
|
|
744
955
|
setPlanMode,
|
|
745
956
|
addSystemMessage,
|
|
746
|
-
addUserMessage
|
|
957
|
+
addUserMessage,
|
|
958
|
+
setCost
|
|
747
959
|
};
|
|
748
960
|
}
|
|
749
961
|
|
|
@@ -797,7 +1009,7 @@ var PERSONA_EMOJIS = {
|
|
|
797
1009
|
function getEmoji(persona) {
|
|
798
1010
|
return PERSONA_EMOJIS[persona] || "\u{1F916}";
|
|
799
1011
|
}
|
|
800
|
-
function useOrchestrator(addMessage2) {
|
|
1012
|
+
function useOrchestrator(addMessage2, setCost) {
|
|
801
1013
|
const [running, setRunning] = useState2(false);
|
|
802
1014
|
const [statusMessage, setStatusMessage] = useState2("");
|
|
803
1015
|
const [confirmRequest, setConfirmRequest] = useState2(null);
|
|
@@ -816,7 +1028,7 @@ function useOrchestrator(addMessage2) {
|
|
|
816
1028
|
setRunning(false);
|
|
817
1029
|
return;
|
|
818
1030
|
}
|
|
819
|
-
const { classifyComplexity, runOrchestration } = await import("./orchestrator-
|
|
1031
|
+
const { classifyComplexity, runOrchestration } = await import("./orchestrator-VRGIOUHT.js");
|
|
820
1032
|
const output = {
|
|
821
1033
|
log(persona, message) {
|
|
822
1034
|
const emoji = getEmoji(persona);
|
|
@@ -880,6 +1092,9 @@ function useOrchestrator(addMessage2) {
|
|
|
880
1092
|
addMessage2(
|
|
881
1093
|
`[${emoji} ${persona}] \u2193 ${toolName}${detail ? " " + detail : ""}`
|
|
882
1094
|
);
|
|
1095
|
+
},
|
|
1096
|
+
updateCost(cost) {
|
|
1097
|
+
setCost?.(cost);
|
|
883
1098
|
}
|
|
884
1099
|
};
|
|
885
1100
|
addMessage2("Analyzing task complexity\u2026");
|
|
@@ -1368,11 +1583,6 @@ function PermissionPrompt({ request }) {
|
|
|
1368
1583
|
init_esm_shims();
|
|
1369
1584
|
import { Box as Box4, Text as Text4, useStdout } from "ink";
|
|
1370
1585
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1371
|
-
function formatTokens(n) {
|
|
1372
|
-
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
1373
|
-
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
1374
|
-
return String(n);
|
|
1375
|
-
}
|
|
1376
1586
|
function formatCost(c) {
|
|
1377
1587
|
if (c < 0.01) return "$0.00";
|
|
1378
1588
|
return `$${c.toFixed(2)}`;
|
|
@@ -1399,7 +1609,8 @@ function StatusBar(props) {
|
|
|
1399
1609
|
}
|
|
1400
1610
|
const bgColor = theme.subtleDark;
|
|
1401
1611
|
const modelStr = ` ${props.provider}/${props.model} `;
|
|
1402
|
-
const
|
|
1612
|
+
const pct = props.maxContext > 0 ? Math.round(usage * 100) : 0;
|
|
1613
|
+
const tokenStr = ` ${pct}% `;
|
|
1403
1614
|
const costStr = formatCost(props.cost);
|
|
1404
1615
|
const branchStr = props.gitBranch ? ` git:(${props.gitBranch})` : "";
|
|
1405
1616
|
const cwdStr = props.cwd ? ` ${props.cwd}` : "";
|
|
@@ -1679,7 +1890,7 @@ function Root(props) {
|
|
|
1679
1890
|
},
|
|
1680
1891
|
[agent]
|
|
1681
1892
|
);
|
|
1682
|
-
const orchestrator = useOrchestrator(addOrchestratorMessage);
|
|
1893
|
+
const orchestrator = useOrchestrator(addOrchestratorMessage, agent.setCost);
|
|
1683
1894
|
const buildStarted = useRef3(false);
|
|
1684
1895
|
useEffect2(() => {
|
|
1685
1896
|
if (props.initialBuildTask && !buildStarted.current) {
|
|
@@ -1997,7 +2208,7 @@ function printWelcome(provider, model, workingDir) {
|
|
|
1997
2208
|
console.log(dim(" Type ") + white("/help") + dim(" for all commands."));
|
|
1998
2209
|
console.log();
|
|
1999
2210
|
}
|
|
2000
|
-
var VERSION = "0.
|
|
2211
|
+
var VERSION = "0.4.0";
|
|
2001
2212
|
function addSharedOptions(cmd) {
|
|
2002
2213
|
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
2214
|
}
|
|
@@ -401,6 +401,7 @@ Available personas: backend_developer, frontend_developer, devops_engineer, qa_e
|
|
|
401
401
|
if (finalText && finalText.length > planText.length) {
|
|
402
402
|
planText = finalText;
|
|
403
403
|
}
|
|
404
|
+
const planUsage = await planStream.totalUsage;
|
|
404
405
|
let stories = parseStoriesFromText(planText, output);
|
|
405
406
|
if (stories.length === 0) {
|
|
406
407
|
output.log("system", "Planner didn't produce structured stories, falling back to single story");
|
|
@@ -411,7 +412,13 @@ Available personas: backend_developer, frontend_developer, devops_engineer, qa_e
|
|
|
411
412
|
description: userTask
|
|
412
413
|
}];
|
|
413
414
|
}
|
|
414
|
-
return
|
|
415
|
+
return {
|
|
416
|
+
stories,
|
|
417
|
+
provider: pProvider,
|
|
418
|
+
model: pModel,
|
|
419
|
+
inputTokens: planUsage?.inputTokens || 0,
|
|
420
|
+
outputTokens: planUsage?.outputTokens || 0
|
|
421
|
+
};
|
|
415
422
|
}
|
|
416
423
|
function parseStoriesFromText(text, output) {
|
|
417
424
|
const codeBlocks = [...text.matchAll(/```(?:json)?\s*\n?([\s\S]*?)```/g)];
|
|
@@ -542,7 +549,10 @@ async function runOrchestration(config, userTask, trustAll, sandboxed, output) {
|
|
|
542
549
|
};
|
|
543
550
|
const sessionAllow = /* @__PURE__ */ new Set();
|
|
544
551
|
const workingDir = process.cwd();
|
|
545
|
-
const
|
|
552
|
+
const planResult = await planStories(config, userTask, workingDir, sandboxed, output);
|
|
553
|
+
const plannerStories = planResult.stories;
|
|
554
|
+
costTracker.addUsage("Planner", planResult.provider, planResult.model, planResult.inputTokens, planResult.outputTokens);
|
|
555
|
+
output.updateCost?.(costTracker.getTotalCost());
|
|
546
556
|
output.log("planner", `Plan generated: ${plannerStories.length} stories`);
|
|
547
557
|
plannerStories.forEach((s, i) => {
|
|
548
558
|
output.log("planner", `Step ${i + 1}: [${s.persona}] ${s.title}${s.dependsOn?.length ? ` (after: ${s.dependsOn.join(", ")})` : ""}`);
|
|
@@ -753,6 +763,7 @@ ${revisionFeedback}` : ""}`;
|
|
|
753
763
|
const inTokens = usage?.inputTokens || 0;
|
|
754
764
|
const outTokens = usage?.outputTokens || 0;
|
|
755
765
|
costTracker.addUsage(persona.name, provider, modelName, inTokens, outTokens);
|
|
766
|
+
output.updateCost?.(costTracker.getTotalCost());
|
|
756
767
|
output.log(story.persona, `${story.title} \u2014 completed! (${i + 1}/${sorted.length})`);
|
|
757
768
|
output.log("system", "");
|
|
758
769
|
break;
|
|
@@ -804,7 +815,7 @@ ${revisionFeedback}` : ""}`;
|
|
|
804
815
|
for (let reviewRound = 0; reviewRound <= maxRevisions; reviewRound++) {
|
|
805
816
|
const isRevision = reviewRound > 0;
|
|
806
817
|
output.coordinatorLog(isRevision ? `Starting Tech Lead review (revision ${reviewRound}/${maxRevisions})...` : "Starting Tech Lead review...");
|
|
807
|
-
output.log("tech_lead",
|
|
818
|
+
output.log("tech_lead", `Starting agent execution (model: ${revModel})`);
|
|
808
819
|
output.status(isRevision ? "Reviewer -- Re-checking after revisions" : "Reviewer -- Checking code quality");
|
|
809
820
|
try {
|
|
810
821
|
const previousFeedbackSection = isRevision && previousReviewFeedback ? `## Previous Review Feedback (Review ${reviewRound}/${maxRevisions})
|
|
@@ -908,6 +919,7 @@ AFFECTED_REASONS: {"2": "Missing error handling in auth controller", "3": "Front
|
|
|
908
919
|
reviewUsage?.inputTokens || 0,
|
|
909
920
|
reviewUsage?.outputTokens || 0
|
|
910
921
|
);
|
|
922
|
+
output.updateCost?.(costTracker.getTotalCost());
|
|
911
923
|
if (approved) break;
|
|
912
924
|
if (reviewRound >= maxRevisions) {
|
|
913
925
|
output.log("system", `Max review revisions (${maxRevisions}) reached`);
|
|
@@ -965,7 +977,7 @@ AFFECTED_REASONS: {"2": "Missing error handling in auth controller", "3": "Front
|
|
|
965
977
|
}
|
|
966
978
|
}
|
|
967
979
|
output.coordinatorLog(`Revision pass for story ${i + 1}/${sorted.length}`);
|
|
968
|
-
output.log(story.persona, `Starting revision: ${story.title}`);
|
|
980
|
+
output.log(story.persona, `Starting revision: ${story.title} (model: ${sModel})`);
|
|
969
981
|
output.status("");
|
|
970
982
|
const storyModel = createModel(sProvider, sModel, sHost, sCtx);
|
|
971
983
|
const storyAllTools = createToolDefinitions(workingDir, storyModel, sandboxed);
|
|
@@ -1038,6 +1050,7 @@ ${story.description}`,
|
|
|
1038
1050
|
revUsage?.inputTokens || 0,
|
|
1039
1051
|
revUsage?.outputTokens || 0
|
|
1040
1052
|
);
|
|
1053
|
+
output.updateCost?.(costTracker.getTotalCost());
|
|
1041
1054
|
output.log(story.persona, `${story.title} \u2014 revision complete!`);
|
|
1042
1055
|
} catch (err) {
|
|
1043
1056
|
output.statusDone();
|