@robmclarty/volley 0.10.1 → 0.11.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.
- package/CHANGELOG.md +27 -0
- package/README.md +65 -9
- package/dist/cli.js +395 -33
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +40 -0
- package/dist/index.js +301 -25
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { accessSync, constants, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import { cac } from "cac";
|
|
6
6
|
import { serve_acp, to_session_updates, workspace_tools } from "fascicle/acp";
|
|
@@ -101,6 +101,187 @@ function step_countdown(max_steps, check) {
|
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
//#endregion
|
|
104
|
+
//#region src/builder/prepare_step.ts
|
|
105
|
+
/** One hook that runs `hooks` in order, each on the request the one before it
|
|
106
|
+
* made, and reports no change when none of them made one. */
|
|
107
|
+
function compose_prepare_steps(hooks) {
|
|
108
|
+
return async ({ step_index, messages }) => {
|
|
109
|
+
let request = messages;
|
|
110
|
+
let changed = false;
|
|
111
|
+
for (const hook of hooks) {
|
|
112
|
+
const prepared = await hook({
|
|
113
|
+
step_index,
|
|
114
|
+
messages: request
|
|
115
|
+
});
|
|
116
|
+
if (prepared?.messages !== void 0) {
|
|
117
|
+
request = prepared.messages;
|
|
118
|
+
changed = true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return changed ? { messages: request } : void 0;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/builder/trim.ts
|
|
126
|
+
/**
|
|
127
|
+
* Trimming a long tool-loop session's history (#7). Every turn resends the
|
|
128
|
+
* whole session, every earlier `read_file` result included, even for files the
|
|
129
|
+
* builder has rewritten since, so a long local session's requests grow
|
|
130
|
+
* linearly and each turn's latency grows with them: volley-41806476's input
|
|
131
|
+
* went from 2.6k to 57k tokens over 50 steps, and its turns from 3 s to as
|
|
132
|
+
* much as 120 s.
|
|
133
|
+
*
|
|
134
|
+
* Above a size the operator sets (`--builder-trim-history`), each turn's
|
|
135
|
+
* request replaces the older tool results with one-line stubs: a read of a
|
|
136
|
+
* file a later call wrote, edited, or read whole says so, and any other result
|
|
137
|
+
* names its call and size. The last `TRIM_KEEP_TURNS` turns, every assistant
|
|
138
|
+
* and user message, every failed call's result, and anything short stay as
|
|
139
|
+
* they are. It goes in through fascicle's `prepare_step`, which reshapes only
|
|
140
|
+
* what one turn sends, so the transcript stays as the model wrote it.
|
|
141
|
+
*
|
|
142
|
+
* A local runtime reuses the KV cache for the prefix a request shares with
|
|
143
|
+
* the last one, and rewriting an earlier message breaks that prefix, so the
|
|
144
|
+
* window is prefilled again. So the trim moves in coarse steps: it stubs
|
|
145
|
+
* everything before a turn boundary, boundaries fall every
|
|
146
|
+
* `TRIM_STRIDE_TURNS` turns, and the boundary moves on only when the trimmed
|
|
147
|
+
* request has grown past the size again. Between moves each request starts
|
|
148
|
+
* with the same bytes as the last, since a stub depends only on what came
|
|
149
|
+
* before its boundary and never on the step. The trim keeps no state:
|
|
150
|
+
* `trim_cutoff` works the boundary out again from the transcript each turn.
|
|
151
|
+
*/
|
|
152
|
+
/** A rough token count per character: the hook sees no token counts, so a
|
|
153
|
+
* request's size is estimated from its text. */
|
|
154
|
+
const CHARS_PER_TOKEN = 4;
|
|
155
|
+
/** What a message costs beyond its text: its role and framing. */
|
|
156
|
+
const MESSAGE_OVERHEAD_TOKENS = 8;
|
|
157
|
+
/** The calls that change a file, whose success makes an earlier read of it
|
|
158
|
+
* stale. Both volley's tools and fascicle's ACP tools use these names and a
|
|
159
|
+
* `path` input. */
|
|
160
|
+
const FILE_WRITERS = /* @__PURE__ */ new Set(["write_file", "edit_file"]);
|
|
161
|
+
const COMMAND_TOOLS = /* @__PURE__ */ new Set(["bash", "run_command"]);
|
|
162
|
+
function message_chars(message) {
|
|
163
|
+
if (typeof message.content === "string") return message.content.length;
|
|
164
|
+
let chars = 0;
|
|
165
|
+
for (const part of message.content) if (part.type === "text" || part.type === "reasoning") chars += part.text.length;
|
|
166
|
+
else if (part.type === "tool_call") chars += part.name.length + (JSON.stringify(part.input) ?? "").length;
|
|
167
|
+
return chars;
|
|
168
|
+
}
|
|
169
|
+
/** A request's size in tokens, estimated from its characters. */
|
|
170
|
+
function estimate_tokens(messages) {
|
|
171
|
+
let tokens = 0;
|
|
172
|
+
for (const message of messages) tokens += Math.ceil(message_chars(message) / CHARS_PER_TOKEN) + MESSAGE_OVERHEAD_TOKENS;
|
|
173
|
+
return tokens;
|
|
174
|
+
}
|
|
175
|
+
function call_input(input) {
|
|
176
|
+
if (typeof input !== "object" || input === null) return {
|
|
177
|
+
path: null,
|
|
178
|
+
whole: true
|
|
179
|
+
};
|
|
180
|
+
const fields = input;
|
|
181
|
+
return {
|
|
182
|
+
path: typeof fields["path"] === "string" ? posix.normalize(fields["path"]) : null,
|
|
183
|
+
whole: fields["offset"] === void 0 && fields["limit"] === void 0
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/** Each tool call's input, by call id, from the assistant messages in
|
|
187
|
+
* `messages`. */
|
|
188
|
+
function call_inputs(messages) {
|
|
189
|
+
const inputs = /* @__PURE__ */ new Map();
|
|
190
|
+
for (const message of messages) {
|
|
191
|
+
if (message.role !== "assistant" || typeof message.content === "string") continue;
|
|
192
|
+
for (const part of message.content) if (part.type === "tool_call") inputs.set(part.id, call_input(part.input));
|
|
193
|
+
}
|
|
194
|
+
return inputs;
|
|
195
|
+
}
|
|
196
|
+
/** Whether this result is one whose call changed its file or read it whole. */
|
|
197
|
+
function supersession_of(message, input) {
|
|
198
|
+
if (input === void 0 || input.path === null || message.is_error === true) return null;
|
|
199
|
+
if (FILE_WRITERS.has(message.name)) return message.content.startsWith("error:") ? null : {
|
|
200
|
+
tool: message.name,
|
|
201
|
+
changed: true
|
|
202
|
+
};
|
|
203
|
+
if (message.name === "read_file" && input.whole) return {
|
|
204
|
+
tool: "read_file",
|
|
205
|
+
changed: false
|
|
206
|
+
};
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
function exit_code_of(content) {
|
|
210
|
+
try {
|
|
211
|
+
const parsed = JSON.parse(content);
|
|
212
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
213
|
+
const code = parsed["exit_code"];
|
|
214
|
+
if (typeof code === "number") return code;
|
|
215
|
+
}
|
|
216
|
+
} catch {}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
function stub(message, input, later) {
|
|
220
|
+
const size = `${String(message.content.length)} chars`;
|
|
221
|
+
if (later !== void 0 && input !== void 0 && input.path !== null) return later.changed ? `[volley] read_file ${input.path} (${size}) elided: a later ${later.tool} changed this file. Read it again for its current content.` : `[volley] read_file ${input.path} (${size}) elided: it was read again later in this session.`;
|
|
222
|
+
const exit_code = COMMAND_TOOLS.has(message.name) ? exit_code_of(message.content) : null;
|
|
223
|
+
const detail = exit_code === null ? size : `${size}, exit ${String(exit_code)}`;
|
|
224
|
+
return `[volley] ${message.name} output (${detail}) elided from an earlier turn. Run it again if you need it.`;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* The request with every tool result before `cutoff` that is long enough to
|
|
228
|
+
* matter and didn't fail replaced by a stub. Every message keeps its place,
|
|
229
|
+
* role, id, and name. A stub reads only what comes before `cutoff`, so the
|
|
230
|
+
* same transcript prefix always gives the same bytes.
|
|
231
|
+
*/
|
|
232
|
+
function trim_view(messages, cutoff) {
|
|
233
|
+
const window = messages.slice(0, cutoff);
|
|
234
|
+
const inputs = call_inputs(window);
|
|
235
|
+
const next_on_path = /* @__PURE__ */ new Map();
|
|
236
|
+
const later_for = /* @__PURE__ */ new Map();
|
|
237
|
+
for (let i = window.length - 1; i >= 0; i -= 1) {
|
|
238
|
+
const message = window[i];
|
|
239
|
+
if (message?.role !== "tool") continue;
|
|
240
|
+
const input = inputs.get(message.tool_call_id);
|
|
241
|
+
const path = input?.path ?? null;
|
|
242
|
+
if (path === null) continue;
|
|
243
|
+
if (message.name === "read_file") {
|
|
244
|
+
const later = next_on_path.get(path);
|
|
245
|
+
if (later !== void 0) later_for.set(i, later);
|
|
246
|
+
}
|
|
247
|
+
const supersedes = supersession_of(message, input);
|
|
248
|
+
if (supersedes !== null) next_on_path.set(path, supersedes);
|
|
249
|
+
}
|
|
250
|
+
return messages.map((message, i) => {
|
|
251
|
+
if (i >= cutoff || message.role !== "tool" || message.is_error === true) return message;
|
|
252
|
+
if (message.content.length <= 200) return message;
|
|
253
|
+
return {
|
|
254
|
+
...message,
|
|
255
|
+
content: stub(message, inputs.get(message.tool_call_id), later_for.get(i))
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Where to cut this turn's request, as a message index, or null to send it
|
|
261
|
+
* whole. Boundaries are the starts of turns `TRIM_STRIDE_TURNS`,
|
|
262
|
+
* `2 * TRIM_STRIDE_TURNS`, and so on, short of the last `TRIM_KEEP_TURNS`.
|
|
263
|
+
* The cut moves to the next boundary only while the request, trimmed at the
|
|
264
|
+
* last one, is still over `threshold`. A trimmed request only grows as the
|
|
265
|
+
* session goes on, so a boundary once taken is kept.
|
|
266
|
+
*/
|
|
267
|
+
function trim_cutoff(messages, threshold) {
|
|
268
|
+
const turn_starts = messages.flatMap((message, i) => message.role === "assistant" ? [i] : []);
|
|
269
|
+
let cutoff = null;
|
|
270
|
+
for (let turn = 8; turn <= turn_starts.length - 6; turn += 8) {
|
|
271
|
+
if (estimate_tokens(cutoff === null ? messages : trim_view(messages, cutoff)) <= threshold) break;
|
|
272
|
+
cutoff = turn_starts[turn] ?? null;
|
|
273
|
+
}
|
|
274
|
+
return cutoff;
|
|
275
|
+
}
|
|
276
|
+
/** The `prepare_step` hook that trims a request estimated above `threshold`
|
|
277
|
+
* tokens. */
|
|
278
|
+
function step_trim(threshold) {
|
|
279
|
+
return ({ messages }) => {
|
|
280
|
+
const cutoff = trim_cutoff(messages, threshold);
|
|
281
|
+
return cutoff === null ? void 0 : { messages: trim_view(messages, cutoff) };
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
//#endregion
|
|
104
285
|
//#region src/types.ts
|
|
105
286
|
function config_error(message) {
|
|
106
287
|
return Object.assign(new Error(message), { kind: "config_error" });
|
|
@@ -310,6 +491,7 @@ function create_volley_engine(options) {
|
|
|
310
491
|
default_cwd: options.workspace,
|
|
311
492
|
setting_sources: ["project", "local"],
|
|
312
493
|
auth_mode,
|
|
494
|
+
omit_env: SEAT_CREDENTIAL_VARS,
|
|
313
495
|
...binary !== void 0 && binary.length > 0 ? { binary } : {},
|
|
314
496
|
...auth_mode === "api_key" && env[ANTHROPIC_KEY_VAR] !== void 0 ? { api_key: env[ANTHROPIC_KEY_VAR] } : {}
|
|
315
497
|
} };
|
|
@@ -1168,6 +1350,60 @@ function rejected_check(runner) {
|
|
|
1168
1350
|
};
|
|
1169
1351
|
}
|
|
1170
1352
|
//#endregion
|
|
1353
|
+
//#region src/claude_cli_permissions.ts
|
|
1354
|
+
/** Whether a seat this run drives runs on the Claude CLI. A critique has no
|
|
1355
|
+
* builder, whatever its provider resolved to. */
|
|
1356
|
+
function uses_claude_cli(config) {
|
|
1357
|
+
return config.mode !== "critique" && config.builder_provider === "claude_cli" || config.critic_provider === "claude_cli";
|
|
1358
|
+
}
|
|
1359
|
+
const BUILDER_ALLOWED_TOOLS = [
|
|
1360
|
+
"Read(/**)",
|
|
1361
|
+
"Edit(/**)",
|
|
1362
|
+
"Bash",
|
|
1363
|
+
"WebFetch"
|
|
1364
|
+
];
|
|
1365
|
+
const CRITIC_ALLOWED_TOOLS = ["Read(/**)"];
|
|
1366
|
+
const CRITIC_DISALLOWED_TOOLS = "Write,Edit,MultiEdit,NotebookEdit,Bash";
|
|
1367
|
+
const PERMISSION_BLOCKS = { permissions: {
|
|
1368
|
+
blockWritesOutsideWorkingDirectories: true,
|
|
1369
|
+
blockReadsOutsideWorkingDirectories: true
|
|
1370
|
+
} };
|
|
1371
|
+
/** The CLI flags that turn on `PERMISSION_BLOCKS`. */
|
|
1372
|
+
function settings_args() {
|
|
1373
|
+
return ["--settings", JSON.stringify(PERMISSION_BLOCKS)];
|
|
1374
|
+
}
|
|
1375
|
+
const MIN_CLAUDE_WRITE_BLOCK = [
|
|
1376
|
+
2,
|
|
1377
|
+
1,
|
|
1378
|
+
314
|
|
1379
|
+
];
|
|
1380
|
+
const MIN_CLAUDE_READ_BLOCK = [
|
|
1381
|
+
2,
|
|
1382
|
+
1,
|
|
1383
|
+
257
|
|
1384
|
+
];
|
|
1385
|
+
/** The version `claude --version` printed (`2.1.316 (Claude Code)`), or null
|
|
1386
|
+
* when it printed something else. */
|
|
1387
|
+
function parse_claude_version(stdout) {
|
|
1388
|
+
const match = /^\s*(\d+)\.(\d+)\.(\d+)\b/.exec(stdout);
|
|
1389
|
+
if (match === null) return null;
|
|
1390
|
+
return [
|
|
1391
|
+
Number(match[1]),
|
|
1392
|
+
Number(match[2]),
|
|
1393
|
+
Number(match[3])
|
|
1394
|
+
];
|
|
1395
|
+
}
|
|
1396
|
+
function version_below(version, min) {
|
|
1397
|
+
const [major, minor, patch] = version;
|
|
1398
|
+
const [min_major, min_minor, min_patch] = min;
|
|
1399
|
+
if (major !== min_major) return major < min_major;
|
|
1400
|
+
if (minor !== min_minor) return minor < min_minor;
|
|
1401
|
+
return patch < min_patch;
|
|
1402
|
+
}
|
|
1403
|
+
function format_version(version) {
|
|
1404
|
+
return version.join(".");
|
|
1405
|
+
}
|
|
1406
|
+
//#endregion
|
|
1171
1407
|
//#region src/cost.ts
|
|
1172
1408
|
/**
|
|
1173
1409
|
* Cost and usage accumulation. All pricing knowledge lives in
|
|
@@ -1223,14 +1459,77 @@ function phase_record(result, model, duration_ms = result.timing?.duration_ms ??
|
|
|
1223
1459
|
...rate !== void 0 ? { throughput: rate } : {}
|
|
1224
1460
|
};
|
|
1225
1461
|
}
|
|
1462
|
+
const NS_PER_MS = 1e6;
|
|
1463
|
+
function ns_field(payload, name) {
|
|
1464
|
+
const value = payload[name];
|
|
1465
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
1466
|
+
}
|
|
1467
|
+
/** A turn's prefill and decode times: Ollama's own durations when it reported
|
|
1468
|
+
* them (in ns), else the time to the first streamed chunk, else none. */
|
|
1469
|
+
function step_split(step) {
|
|
1470
|
+
const reported = step.provider_reported?.["ollama"];
|
|
1471
|
+
if (typeof reported === "object" && reported !== null) {
|
|
1472
|
+
const ollama = reported;
|
|
1473
|
+
const evaluated = ns_field(ollama, "eval_duration");
|
|
1474
|
+
const prompt = ns_field(ollama, "prompt_eval_duration");
|
|
1475
|
+
if (evaluated !== void 0 && prompt !== void 0) return {
|
|
1476
|
+
prefill_ms: Math.round(prompt / NS_PER_MS),
|
|
1477
|
+
decode_ms: Math.round(evaluated / NS_PER_MS),
|
|
1478
|
+
timing_basis: "ollama_reported"
|
|
1479
|
+
};
|
|
1480
|
+
const total = ns_field(ollama, "total_duration");
|
|
1481
|
+
const load = ns_field(ollama, "load_duration");
|
|
1482
|
+
if (evaluated !== void 0 && total !== void 0 && load !== void 0 && total >= load + evaluated) return {
|
|
1483
|
+
prefill_ms: Math.round((total - load - evaluated) / NS_PER_MS),
|
|
1484
|
+
decode_ms: Math.round(evaluated / NS_PER_MS),
|
|
1485
|
+
timing_basis: "ollama_derived"
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
const timing = step.timing;
|
|
1489
|
+
if (timing?.first_chunk_ms !== void 0 && timing.whole_tool_call !== true) return {
|
|
1490
|
+
prefill_ms: timing.first_chunk_ms,
|
|
1491
|
+
decode_ms: Math.max(0, timing.duration_ms - timing.first_chunk_ms),
|
|
1492
|
+
timing_basis: "first_chunk"
|
|
1493
|
+
};
|
|
1494
|
+
return { timing_basis: "none" };
|
|
1495
|
+
}
|
|
1496
|
+
/** How much a tool call's result put into the transcript: its output as the
|
|
1497
|
+
* loop serializes it, or the error text it fed back. */
|
|
1498
|
+
function output_chars(call) {
|
|
1499
|
+
if (call.output === void 0) return call.error?.message.length ?? 0;
|
|
1500
|
+
return typeof call.output === "string" ? call.output.length : (JSON.stringify(call.output) ?? "").length;
|
|
1501
|
+
}
|
|
1502
|
+
/** Each turn of a session as the archive records it, or undefined when the
|
|
1503
|
+
* result has none. */
|
|
1504
|
+
function step_records(result) {
|
|
1505
|
+
if (result.steps.length === 0) return void 0;
|
|
1506
|
+
return result.steps.map((step) => ({
|
|
1507
|
+
index: step.index,
|
|
1508
|
+
input_tokens: step.usage.input_tokens,
|
|
1509
|
+
output_tokens: step.usage.output_tokens,
|
|
1510
|
+
duration_ms: step.timing?.duration_ms ?? 0,
|
|
1511
|
+
...step_split(step),
|
|
1512
|
+
finish_reason: step.finish_reason,
|
|
1513
|
+
...step.truncated === true ? { truncated: true } : {},
|
|
1514
|
+
tool_calls: step.tool_calls.map((call) => ({
|
|
1515
|
+
name: call.name,
|
|
1516
|
+
output_chars: output_chars(call)
|
|
1517
|
+
}))
|
|
1518
|
+
}));
|
|
1519
|
+
}
|
|
1226
1520
|
/** Fold one phase's result into the loop state. Unknown cost records `null`
|
|
1227
|
-
* for the phase and warns once per run — a run never fails over pricing.
|
|
1521
|
+
* for the phase and warns once per run — a run never fails over pricing. The
|
|
1522
|
+
* builder's record also carries its session's turns. */
|
|
1228
1523
|
function accumulate(state, role, result, model, duration_ms) {
|
|
1229
1524
|
const record = phase_record(result, model, duration_ms);
|
|
1525
|
+
const steps = role === "builder" ? step_records(result) : void 0;
|
|
1230
1526
|
const cost = record.cost_usd ?? 0;
|
|
1231
1527
|
return {
|
|
1232
1528
|
...state,
|
|
1233
|
-
[role]:
|
|
1529
|
+
[role]: steps !== void 0 ? {
|
|
1530
|
+
...record,
|
|
1531
|
+
steps
|
|
1532
|
+
} : record,
|
|
1234
1533
|
total_usage: add_usage(state.total_usage, result.usage),
|
|
1235
1534
|
total_cost_usd: state.total_cost_usd + cost,
|
|
1236
1535
|
iteration_cost_usd: state.iteration_cost_usd + cost,
|
|
@@ -1478,6 +1777,7 @@ function write_resolved_config(config, env = process.env) {
|
|
|
1478
1777
|
builder_max_steps: config.builder_max_steps,
|
|
1479
1778
|
builder_max_tool_calls: config.builder_max_tool_calls,
|
|
1480
1779
|
builder_max_output_tokens: config.builder_max_output_tokens,
|
|
1780
|
+
builder_trim_history: config.builder_trim_history,
|
|
1481
1781
|
critic_model: config.critic_model,
|
|
1482
1782
|
critic_provider: config.critic_provider,
|
|
1483
1783
|
critic_url: config.critic_url,
|
|
@@ -1951,16 +2251,6 @@ async function with_worktree(options, body) {
|
|
|
1951
2251
|
* complete agentic session in the workspace — Claude Code under `claude_cli`,
|
|
1952
2252
|
* or a model driving volley's tools on any other provider, local or hosted.
|
|
1953
2253
|
*/
|
|
1954
|
-
const BUILDER_ALLOWED_TOOLS = [
|
|
1955
|
-
"Read",
|
|
1956
|
-
"Write",
|
|
1957
|
-
"Edit",
|
|
1958
|
-
"MultiEdit",
|
|
1959
|
-
"Bash",
|
|
1960
|
-
"Grep",
|
|
1961
|
-
"Glob",
|
|
1962
|
-
"WebFetch"
|
|
1963
|
-
];
|
|
1964
2254
|
function compose_builder_system() {
|
|
1965
2255
|
return [
|
|
1966
2256
|
"You are the builder inside the volley harness: an autonomous agent",
|
|
@@ -2022,6 +2312,14 @@ function check_hint(config) {
|
|
|
2022
2312
|
if (config.check_resolved === "command") return `the check (\`${config.check}\`)`;
|
|
2023
2313
|
return "the project's tests";
|
|
2024
2314
|
}
|
|
2315
|
+
/** The builder's `prepare_step`: the countdown, after the history trim when
|
|
2316
|
+
* `--builder-trim-history` turns it on, so the countdown's note still goes
|
|
2317
|
+
* last. */
|
|
2318
|
+
function builder_prepare_step(config) {
|
|
2319
|
+
const countdown = step_countdown(config.builder_max_steps, check_hint(config));
|
|
2320
|
+
if (config.builder_trim_history === null) return countdown;
|
|
2321
|
+
return compose_prepare_steps([step_trim(config.builder_trim_history), countdown]);
|
|
2322
|
+
}
|
|
2025
2323
|
/** Per-provider tool wiring for the builder, mirroring `critic_tool_options`.
|
|
2026
2324
|
* The `claude_cli` builder is confined at the CLI permission layer (allowlist +
|
|
2027
2325
|
* permission mode) and brings its own built-in tools — this arm is unchanged
|
|
@@ -2029,7 +2327,8 @@ function check_hint(config) {
|
|
|
2029
2327
|
* builder, local or `openrouter`, brings none, so volley supplies its whole
|
|
2030
2328
|
* workspace tool surface (or the set the caller injected in its place, with
|
|
2031
2329
|
* the handler that answers its approvals), the per-call loop knobs, the step
|
|
2032
|
-
* countdown (`./builder/countdown.ts`)
|
|
2330
|
+
* countdown (`./builder/countdown.ts`) and the history trim when it's on
|
|
2331
|
+
* (`./builder/trim.ts`), the output cap when one is set, and
|
|
2033
2332
|
* **no** schema (it produces a workspace, not a verdict); per-call values win
|
|
2034
2333
|
* over engine defaults. */
|
|
2035
2334
|
function builder_tool_options(deps) {
|
|
@@ -2037,7 +2336,11 @@ function builder_tool_options(deps) {
|
|
|
2037
2336
|
const bash_executor = deps.bash_executor ?? null;
|
|
2038
2337
|
if (config.builder_provider === "claude_cli") return { provider_options: { claude_cli: {
|
|
2039
2338
|
allowed_tools: [...BUILDER_ALLOWED_TOOLS],
|
|
2040
|
-
extra_args: [
|
|
2339
|
+
extra_args: [
|
|
2340
|
+
"--permission-mode",
|
|
2341
|
+
config.builder_permission_mode,
|
|
2342
|
+
...settings_args()
|
|
2343
|
+
]
|
|
2041
2344
|
} } };
|
|
2042
2345
|
return {
|
|
2043
2346
|
tools: deps.tools === void 0 ? builder_tools(build_root(config.workspace, config.worktree), bash_executor !== null ? { bash_executor } : {}) : [...deps.tools],
|
|
@@ -2046,7 +2349,7 @@ function builder_tool_options(deps) {
|
|
|
2046
2349
|
tool_error_policy: "feed_back",
|
|
2047
2350
|
tool_call_repair_attempts: BUILDER_TOOL_CALL_REPAIR_ATTEMPTS,
|
|
2048
2351
|
max_tool_calls_per_step: config.builder_max_tool_calls,
|
|
2049
|
-
prepare_step:
|
|
2352
|
+
prepare_step: builder_prepare_step(config),
|
|
2050
2353
|
...config.builder_max_output_tokens !== null ? {
|
|
2051
2354
|
max_tokens: config.builder_max_output_tokens,
|
|
2052
2355
|
truncated_turn_policy: "continue"
|
|
@@ -2573,12 +2876,6 @@ function compose_critic_prompt(input) {
|
|
|
2573
2876
|
* structured verdict. The
|
|
2574
2877
|
* harness — not the critic — writes the run's `feedback.md` and `verdict`.
|
|
2575
2878
|
*/
|
|
2576
|
-
const CRITIC_ALLOWED_TOOLS = [
|
|
2577
|
-
"Read",
|
|
2578
|
-
"Grep",
|
|
2579
|
-
"Glob"
|
|
2580
|
-
];
|
|
2581
|
-
const CRITIC_DISALLOWED_TOOLS = "Write,Edit,MultiEdit,NotebookEdit,Bash";
|
|
2582
2879
|
/** A critic that holds tools isn't constrain-decoded, so nothing stops it
|
|
2583
2880
|
* answering null for the feedback or the unmet list, as GLM did on approving.
|
|
2584
2881
|
* Both fields allow null and read it as empty, so that answer is a verdict
|
|
@@ -2591,7 +2888,8 @@ const verdict_schema = z.object({
|
|
|
2591
2888
|
unmet_criteria: z.array(z.string()).nullable().transform((unmet) => unmet ?? []).describe("The specific acceptance criteria judged unmet, verbatim. Empty when approved.")
|
|
2592
2889
|
});
|
|
2593
2890
|
/** Read-only tool wiring per provider. The claude_cli critic is confined at
|
|
2594
|
-
* the CLI permission layer (allowlist + explicit disallow
|
|
2891
|
+
* the CLI permission layer (allowlist + explicit disallow, and the settings
|
|
2892
|
+
* blocks that keep its reads in the build root); every other critic,
|
|
2595
2893
|
* local or `openrouter`, gets volley's own workspace-scoped read-only tools (or
|
|
2596
2894
|
* the set the caller injected in their place) and no write path at all.
|
|
2597
2895
|
*
|
|
@@ -2613,7 +2911,11 @@ const verdict_schema = z.object({
|
|
|
2613
2911
|
function critic_tool_options(config, tools) {
|
|
2614
2912
|
if (config.critic_provider === "claude_cli") return { provider_options: { claude_cli: {
|
|
2615
2913
|
allowed_tools: [...CRITIC_ALLOWED_TOOLS],
|
|
2616
|
-
extra_args: [
|
|
2914
|
+
extra_args: [
|
|
2915
|
+
"--disallowedTools",
|
|
2916
|
+
CRITIC_DISALLOWED_TOOLS,
|
|
2917
|
+
...settings_args()
|
|
2918
|
+
]
|
|
2617
2919
|
} } };
|
|
2618
2920
|
return {
|
|
2619
2921
|
tools: tools === void 0 ? read_only_tools(build_root(config.workspace, config.worktree)) : [...tools],
|
|
@@ -2979,7 +3281,8 @@ function phase_summary(record) {
|
|
|
2979
3281
|
...record.retry_cause_kind !== void 0 ? { retry_cause_kind: record.retry_cause_kind } : {},
|
|
2980
3282
|
...record.critic_degraded === true ? { critic_degraded: true } : {},
|
|
2981
3283
|
...record.degrade_cause !== void 0 ? { degrade_cause: record.degrade_cause } : {},
|
|
2982
|
-
...record.throughput !== void 0 ? { throughput: record.throughput } : {}
|
|
3284
|
+
...record.throughput !== void 0 ? { throughput: record.throughput } : {},
|
|
3285
|
+
...record.steps !== void 0 ? { steps: record.steps } : {}
|
|
2983
3286
|
};
|
|
2984
3287
|
}
|
|
2985
3288
|
/** Archive one completed iteration: verdict files, check artifacts, and the
|
|
@@ -3236,6 +3539,7 @@ function load_resume_state(workspace, run_id, state_dir = null, options = {}) {
|
|
|
3236
3539
|
...recorded["builder_max_steps"] === void 0 ? {} : { builder_max_steps: Number(recorded["builder_max_steps"]) },
|
|
3237
3540
|
...recorded["builder_max_tool_calls"] === void 0 ? {} : { builder_max_tool_calls: Number(recorded["builder_max_tool_calls"]) },
|
|
3238
3541
|
...typeof recorded["builder_max_output_tokens"] === "number" ? { builder_max_output_tokens: recorded["builder_max_output_tokens"] } : {},
|
|
3542
|
+
...typeof recorded["builder_trim_history"] === "number" ? { builder_trim_history: recorded["builder_trim_history"] } : {},
|
|
3239
3543
|
critic_model: String(recorded["critic_model"]),
|
|
3240
3544
|
critic_provider: recorded["critic_provider"] ?? "claude_cli",
|
|
3241
3545
|
...typeof recorded["critic_url"] === "string" ? { critic_url: recorded["critic_url"] } : {},
|
|
@@ -4165,6 +4469,7 @@ const CRITIQUE_REFUSED = [
|
|
|
4165
4469
|
["builder_max_steps", "--builder-max-steps"],
|
|
4166
4470
|
["builder_max_tool_calls", "--builder-max-tool-calls"],
|
|
4167
4471
|
["builder_max_output_tokens", "--builder-max-output-tokens"],
|
|
4472
|
+
["builder_trim_history", "--builder-trim-history"],
|
|
4168
4473
|
["builder_permission_mode", "--builder-permission-mode"],
|
|
4169
4474
|
["allow_unsandboxed_builder", "--allow-unsandboxed-builder"],
|
|
4170
4475
|
["sandbox_image", "--sandbox-image"],
|
|
@@ -4204,6 +4509,12 @@ function builder_max_output_tokens_count(provider) {
|
|
|
4204
4509
|
fallback: CAPPED_BY_DEFAULT.includes(provider) ? DEFAULT_BUILDER_MAX_OUTPUT_TOKENS : null
|
|
4205
4510
|
};
|
|
4206
4511
|
}
|
|
4512
|
+
/** The history trim's size: off unless something sets it. */
|
|
4513
|
+
const BUILDER_TRIM_HISTORY = {
|
|
4514
|
+
flag: "--builder-trim-history",
|
|
4515
|
+
env_var: "VOLLEY_BUILDER_TRIM_HISTORY",
|
|
4516
|
+
fallback: null
|
|
4517
|
+
};
|
|
4207
4518
|
/** A builder count's flag or config value wins over its env var, which wins
|
|
4208
4519
|
* over the default, and either must be a positive integer. Local builder
|
|
4209
4520
|
* only; ignored by the `claude_cli` arm. */
|
|
@@ -4338,6 +4649,7 @@ function resolve_config(raw, options = {}) {
|
|
|
4338
4649
|
const builder_max_steps = resolve_builder_count(raw.builder_max_steps, builder_env, BUILDER_MAX_STEPS);
|
|
4339
4650
|
const builder_max_tool_calls = resolve_builder_count(raw.builder_max_tool_calls, builder_env, BUILDER_MAX_TOOL_CALLS);
|
|
4340
4651
|
const builder_max_output_tokens = resolve_builder_count(raw.builder_max_output_tokens, builder_env, builder_max_output_tokens_count(builder_provider));
|
|
4652
|
+
const builder_trim_history = resolve_builder_count(raw.builder_trim_history, builder_env, BUILDER_TRIM_HISTORY);
|
|
4341
4653
|
const critic_provider = raw.critic_provider ?? DEFAULT_CRITIC_PROVIDER;
|
|
4342
4654
|
if (!CRITIC_PROVIDERS.includes(critic_provider)) throw config_error(`--critic-provider must be one of ${CRITIC_PROVIDERS.join(", ")}; got: ${String(raw.critic_provider)}`);
|
|
4343
4655
|
const builder_url = resolve_seat_url("builder", raw.builder_url, builder_provider);
|
|
@@ -4376,6 +4688,7 @@ function resolve_config(raw, options = {}) {
|
|
|
4376
4688
|
builder_max_steps,
|
|
4377
4689
|
builder_max_tool_calls,
|
|
4378
4690
|
builder_max_output_tokens,
|
|
4691
|
+
builder_trim_history,
|
|
4379
4692
|
allow_unsandboxed_builder,
|
|
4380
4693
|
critic_model: raw.critic_model ?? DEFAULT_CRITIC_MODEL,
|
|
4381
4694
|
critic_provider,
|
|
@@ -5471,6 +5784,10 @@ function render_matrix_table(rows) {
|
|
|
5471
5784
|
* never requires Docker or a model server — so they never contribute an exit 5
|
|
5472
5785
|
* on that path.
|
|
5473
5786
|
*
|
|
5787
|
+
* A `claude_cli` seat gets one read of `claude --version`, which only ever
|
|
5788
|
+
* warns: below the version that enforces the settings blocks keeping the seat's
|
|
5789
|
+
* file tools in the build root, the dry run says what goes unenforced.
|
|
5790
|
+
*
|
|
5474
5791
|
* A *local critic* additionally gets the critic-seat canary: one tiny $0
|
|
5475
5792
|
* generate through the real critic tool wiring, run
|
|
5476
5793
|
* whatever the builder provider is. A degradable failure warns and predicts
|
|
@@ -5492,6 +5809,7 @@ const REQUIRED_TOOLCHAIN = [
|
|
|
5492
5809
|
/** A short cap on the endpoint probe so a wrong/unreachable host fails fast
|
|
5493
5810
|
* rather than hanging the preflight before the loop even starts. */
|
|
5494
5811
|
const ENDPOINT_PROBE_TIMEOUT_MS = 3e3;
|
|
5812
|
+
const CLAUDE_VERSION_TIMEOUT_MS = 1e4;
|
|
5495
5813
|
/** Builders that drive their model through volley's own tool loop, local or
|
|
5496
5814
|
* hosted, so the containment preflight applies. `claude_cli` never does. */
|
|
5497
5815
|
function is_tool_loop_builder(provider) {
|
|
@@ -5580,6 +5898,33 @@ function worktree_creatable(workspace) {
|
|
|
5580
5898
|
detail: repo
|
|
5581
5899
|
};
|
|
5582
5900
|
}
|
|
5901
|
+
/** What `claude --version` printed, from the binary the run will spawn, or
|
|
5902
|
+
* null when it didn't run. */
|
|
5903
|
+
function claude_version(env) {
|
|
5904
|
+
const binary = env["VOLLEY_CLAUDE_BIN"];
|
|
5905
|
+
const result = spawnSync(binary !== void 0 && binary.length > 0 ? binary : "claude", ["--version"], {
|
|
5906
|
+
env: command_env(process.env),
|
|
5907
|
+
encoding: "utf8",
|
|
5908
|
+
timeout: CLAUDE_VERSION_TIMEOUT_MS
|
|
5909
|
+
});
|
|
5910
|
+
if (result.error !== void 0 || result.status !== 0) return null;
|
|
5911
|
+
return result.stdout;
|
|
5912
|
+
}
|
|
5913
|
+
/** Name the Claude Code version a `claude_cli` seat runs on, and warn when it
|
|
5914
|
+
* is too old to enforce the blocks that keep the seat's file tools in the
|
|
5915
|
+
* build root. Never fails the dry run. */
|
|
5916
|
+
function report_claude_cli(config, renderer, env, probe) {
|
|
5917
|
+
if (!uses_claude_cli(config)) return;
|
|
5918
|
+
const printed = probe(env);
|
|
5919
|
+
const version = printed === null ? null : parse_claude_version(printed);
|
|
5920
|
+
if (version === null) {
|
|
5921
|
+
renderer.warn("claude: could not read the Claude Code version (`claude --version`), so whether it keeps a claude_cli seat's file tools in the build root is unknown");
|
|
5922
|
+
return;
|
|
5923
|
+
}
|
|
5924
|
+
renderer.info(`claude: ${format_version(version)}`);
|
|
5925
|
+
if (version_below(version, MIN_CLAUDE_WRITE_BLOCK)) renderer.warn(`claude: Claude Code ${format_version(version)} ignores the block on writes outside the build root (needs ${format_version(MIN_CLAUDE_WRITE_BLOCK)}). Edit(/**) still keeps a builder's edits there under acceptEdits, but not under bypassPermissions.`);
|
|
5926
|
+
if (version_below(version, MIN_CLAUDE_READ_BLOCK)) renderer.warn(`claude: Claude Code ${format_version(version)} ignores the block on reads outside the build root (needs ${format_version(MIN_CLAUDE_READ_BLOCK)}).`);
|
|
5927
|
+
}
|
|
5583
5928
|
/** Reachability probe: any HTTP response (even a 404) proves the TCP connect
|
|
5584
5929
|
* landed; a thrown error (connection refused, DNS failure, timeout) means the
|
|
5585
5930
|
* endpoint is unreachable. */
|
|
@@ -5669,6 +6014,7 @@ async function preflight(config, renderer, env, probes = {}) {
|
|
|
5669
6014
|
renderer.info(`builder max steps: ${config.builder_max_steps}`);
|
|
5670
6015
|
renderer.info(`builder tool calls per turn: ${config.builder_max_tool_calls}`);
|
|
5671
6016
|
renderer.info(`builder output tokens per turn: ${config.builder_max_output_tokens ?? "the provider's limit"}`);
|
|
6017
|
+
renderer.info(`builder history trim: ${config.builder_trim_history === null ? "off" : `above ${String(config.builder_trim_history)} estimated tokens`}`);
|
|
5672
6018
|
}
|
|
5673
6019
|
if (tool_loop_builder) {
|
|
5674
6020
|
const contained = detect_containment(env);
|
|
@@ -5679,6 +6025,7 @@ async function preflight(config, renderer, env, probes = {}) {
|
|
|
5679
6025
|
renderer.info(seat_line("critic", config.critic_model, config.critic_provider, config.critic_url, env));
|
|
5680
6026
|
report_worktree_fate(config, renderer);
|
|
5681
6027
|
report_gate_posture(config, renderer);
|
|
6028
|
+
report_claude_cli(config, renderer, env, probes.claude_version ?? claude_version);
|
|
5682
6029
|
if (config.check_resolved === "checkride") {
|
|
5683
6030
|
if (!(probes.checkride_doctor ?? run_checkride_doctor)(config.workspace)) {
|
|
5684
6031
|
renderer.error("checkride doctor failed; fix the workspace check pipeline before running");
|
|
@@ -5776,8 +6123,19 @@ function make_renderer(config, run_id = config.run_id) {
|
|
|
5776
6123
|
}, "trajectory.jsonl")
|
|
5777
6124
|
});
|
|
5778
6125
|
}
|
|
5779
|
-
|
|
5780
|
-
|
|
6126
|
+
/** Only `VOLLEY_AUTH_MODE=api_key` hands the claude CLI a key: under `auto`
|
|
6127
|
+
* and `oauth` it gets none and stays on its own login, whatever is set. */
|
|
6128
|
+
function warn_api_key_meter(config, renderer, env = process.env) {
|
|
6129
|
+
if (uses_claude_cli(config) && env["VOLLEY_AUTH_MODE"] === "api_key") renderer.warn("VOLLEY_AUTH_MODE=api_key: the claude CLI bills this run to ANTHROPIC_API_KEY at API rates, not to a subscription. Know which meter you are on.");
|
|
6130
|
+
}
|
|
6131
|
+
/** What to try when a `claude_cli` seat on the subscription login can't
|
|
6132
|
+
* authenticate, which is how every such seat fails once `claude -p` runs bare by
|
|
6133
|
+
* default (volley#9). Null for any other error. */
|
|
6134
|
+
function claude_login_hint(err, env = process.env) {
|
|
6135
|
+
const auth_failure = [err, err?.cause].some((e) => error_kind(e) === "provider_auth_error" && e.provider === "claude_cli");
|
|
6136
|
+
const auth_mode = env["VOLLEY_AUTH_MODE"] ?? "auto";
|
|
6137
|
+
if (!auth_failure || auth_mode === "api_key") return null;
|
|
6138
|
+
return `a claude_cli seat on the subscription login (VOLLEY_AUTH_MODE=${auth_mode}) could not authenticate. If \`claude auth login\` does not fix it, a Claude Code release that runs \`claude -p\` bare by default would explain it (volley#9): set VOLLEY_AUTH_MODE=api_key with ANTHROPIC_API_KEY to run on the API key meanwhile.`;
|
|
5781
6139
|
}
|
|
5782
6140
|
/** One loud warning when a tool-loop builder is about to run *uncontained* on
|
|
5783
6141
|
* the host via the opt-out. Config resolution has already refused this
|
|
@@ -5800,7 +6158,7 @@ function with_critic_options(command, required = false) {
|
|
|
5800
6158
|
function with_seat_options(command, required = false) {
|
|
5801
6159
|
const model_note = required ? " (required)" : "";
|
|
5802
6160
|
const provider_note = required ? "required" : "default: claude_cli";
|
|
5803
|
-
return with_critic_options(command.option("--builder-model <model>", `Builder model${model_note}`).option("--builder-provider <name>", `claude_cli | ollama | lmstudio | openrouter (${provider_note})`).option("--builder-url <url>", "Builder's model server base URL, ollama, lmstudio, or openrouter only (default: VOLLEY_OLLAMA_URL / VOLLEY_LMSTUDIO_URL / VOLLEY_OPENROUTER_URL)").option("--builder-max-steps <n>", "Tool-loop builder step cap per iteration, any provider but claude_cli (default: 50)").option("--builder-max-tool-calls <n>", "Tool calls a tool-loop builder may make per turn, reads aside, any provider but claude_cli (default: 1)").option("--builder-max-output-tokens <n>", "Output tokens a tool-loop builder may generate per turn, reasoning included, any provider but claude_cli (default: 16384 on ollama and lmstudio, the provider's on openrouter)").option("--allow-unsandboxed-builder", "Permit a builder other than claude_cli to run without a sandbox (real host bash + write)"), required);
|
|
6161
|
+
return with_critic_options(command.option("--builder-model <model>", `Builder model${model_note}`).option("--builder-provider <name>", `claude_cli | ollama | lmstudio | openrouter (${provider_note})`).option("--builder-url <url>", "Builder's model server base URL, ollama, lmstudio, or openrouter only (default: VOLLEY_OLLAMA_URL / VOLLEY_LMSTUDIO_URL / VOLLEY_OPENROUTER_URL)").option("--builder-max-steps <n>", "Tool-loop builder step cap per iteration, any provider but claude_cli (default: 50)").option("--builder-max-tool-calls <n>", "Tool calls a tool-loop builder may make per turn, reads aside, any provider but claude_cli (default: 1)").option("--builder-max-output-tokens <n>", "Output tokens a tool-loop builder may generate per turn, reasoning included, any provider but claude_cli (default: 16384 on ollama and lmstudio, the provider's on openrouter)").option("--builder-trim-history <tokens>", "Experimental: above this estimated request size, a tool-loop builder's turns send older tool results as one-line stubs, any provider but claude_cli (default: off)").option("--allow-unsandboxed-builder", "Permit a builder other than claude_cli to run without a sandbox (real host bash + write)"), required);
|
|
5804
6162
|
}
|
|
5805
6163
|
/** What a critique declares only so that it can refuse each by name, with
|
|
5806
6164
|
* exit 5: cac would answer an undeclared one with a bare "Unknown option".
|
|
@@ -5812,6 +6170,7 @@ const CRITIQUE_REFUSED_OPTIONS = [
|
|
|
5812
6170
|
"--builder-max-steps <n>",
|
|
5813
6171
|
"--builder-max-tool-calls <n>",
|
|
5814
6172
|
"--builder-max-output-tokens <n>",
|
|
6173
|
+
"--builder-trim-history <tokens>",
|
|
5815
6174
|
"--builder-permission-mode <mode>",
|
|
5816
6175
|
"--allow-unsandboxed-builder",
|
|
5817
6176
|
"--sandbox-image <tag>",
|
|
@@ -5836,6 +6195,7 @@ function merge_flags(base, flags) {
|
|
|
5836
6195
|
...flags.builderMaxSteps !== void 0 ? { builder_max_steps: Number(flags.builderMaxSteps) } : {},
|
|
5837
6196
|
...flags.builderMaxToolCalls !== void 0 ? { builder_max_tool_calls: Number(flags.builderMaxToolCalls) } : {},
|
|
5838
6197
|
...flags.builderMaxOutputTokens !== void 0 ? { builder_max_output_tokens: Number(flags.builderMaxOutputTokens) } : {},
|
|
6198
|
+
...flags.builderTrimHistory !== void 0 ? { builder_trim_history: Number(flags.builderTrimHistory) } : {},
|
|
5839
6199
|
...flags.allowUnsandboxedBuilder === true ? { allow_unsandboxed_builder: true } : {},
|
|
5840
6200
|
...flags.criticModel !== void 0 ? { critic_model: flags.criticModel } : {},
|
|
5841
6201
|
...flags.criticProvider !== void 0 ? { critic_provider: flags.criticProvider } : {},
|
|
@@ -5884,6 +6244,8 @@ function report_error(err) {
|
|
|
5884
6244
|
const cause = err.cause;
|
|
5885
6245
|
if (cause instanceof Error && cause.message !== message) process.stderr.write(` cause: ${cause.message}\n`);
|
|
5886
6246
|
}
|
|
6247
|
+
const hint = claude_login_hint(err);
|
|
6248
|
+
if (hint !== null) process.stderr.write(` hint: ${hint}\n`);
|
|
5887
6249
|
return exit_code_for_error(err);
|
|
5888
6250
|
}
|
|
5889
6251
|
async function main(argv) {
|
|
@@ -5894,7 +6256,7 @@ async function main(argv) {
|
|
|
5894
6256
|
const run_id = not_a_bare_number("--run-id", flags.runId);
|
|
5895
6257
|
const config = resolve_config(merged, run_id !== void 0 ? { run_id } : {});
|
|
5896
6258
|
const renderer = make_renderer(config);
|
|
5897
|
-
warn_api_key_meter(renderer);
|
|
6259
|
+
warn_api_key_meter(config, renderer);
|
|
5898
6260
|
warn_unsandboxed_builder(config, renderer);
|
|
5899
6261
|
if (merged.dry_run === true) {
|
|
5900
6262
|
exit_code = await preflight(config, renderer, process.env);
|
|
@@ -5913,7 +6275,7 @@ async function main(argv) {
|
|
|
5913
6275
|
});
|
|
5914
6276
|
if (resume.iterations_completed >= config.max_iterations) throw config_error(`run already completed ${String(resume.iterations_completed)} of ${String(config.max_iterations)} iterations`);
|
|
5915
6277
|
const renderer = make_renderer(config);
|
|
5916
|
-
warn_api_key_meter(renderer);
|
|
6278
|
+
warn_api_key_meter(config, renderer);
|
|
5917
6279
|
warn_unsandboxed_builder(config, renderer);
|
|
5918
6280
|
if (resume.config_signed === false) warn_trusted_config(config, renderer);
|
|
5919
6281
|
renderer.info(`resuming run ${run_id} at iteration ${String(resume.iterations_completed + 1)}`);
|
|
@@ -5932,7 +6294,7 @@ async function main(argv) {
|
|
|
5932
6294
|
...run_id !== void 0 ? { run_id } : {}
|
|
5933
6295
|
});
|
|
5934
6296
|
const renderer = make_renderer(config);
|
|
5935
|
-
warn_api_key_meter(renderer);
|
|
6297
|
+
warn_api_key_meter(config, renderer);
|
|
5936
6298
|
if (merged.dry_run === true) {
|
|
5937
6299
|
const sha = resolve_baseline(config.workspace, baseline);
|
|
5938
6300
|
renderer.info(`baseline: ${sha ?? "none (not a git repository, or no commit yet)"}`);
|
|
@@ -5961,7 +6323,7 @@ async function main(argv) {
|
|
|
5961
6323
|
discard_worktree: true
|
|
5962
6324
|
});
|
|
5963
6325
|
const renderer = make_renderer(base_config, "<run id>");
|
|
5964
|
-
warn_api_key_meter(renderer);
|
|
6326
|
+
warn_api_key_meter(base_config, renderer);
|
|
5965
6327
|
warn_unsandboxed_builder(base_config, renderer);
|
|
5966
6328
|
const outcome = await run_matrix({
|
|
5967
6329
|
base: merged,
|
|
@@ -5993,6 +6355,6 @@ if (import.meta.main) {
|
|
|
5993
6355
|
process.exitCode = code;
|
|
5994
6356
|
}
|
|
5995
6357
|
//#endregion
|
|
5996
|
-
export { package_version, warn_unsandboxed_builder };
|
|
6358
|
+
export { claude_login_hint, package_version, warn_api_key_meter, warn_unsandboxed_builder };
|
|
5997
6359
|
|
|
5998
6360
|
//# sourceMappingURL=cli.js.map
|