@robmclarty/volley 0.5.4 → 0.6.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/CHANGELOG.md +12 -0
- package/README.md +5 -1
- package/dist/cli.js +56 -41
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +86 -3
- package/dist/index.js +197 -80
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/config.ts
|
|
2
|
-
import { accessSync, constants, existsSync, readFileSync, statSync } from "fs";
|
|
2
|
+
import { accessSync, constants, existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "fs";
|
|
3
3
|
import { isAbsolute, resolve } from "path";
|
|
4
4
|
import { pathToFileURL } from "url";
|
|
5
5
|
import { randomUUID } from "crypto";
|
|
@@ -133,6 +133,31 @@ function collect_changes(options) {
|
|
|
133
133
|
};
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
// src/check/detect.ts
|
|
137
|
+
import { existsSync, readFileSync } from "fs";
|
|
138
|
+
import { join } from "path";
|
|
139
|
+
function package_check_script_is_checkride(workspace) {
|
|
140
|
+
const path = join(workspace, "package.json");
|
|
141
|
+
if (!existsSync(path)) return false;
|
|
142
|
+
try {
|
|
143
|
+
const pkg = JSON.parse(readFileSync(path, "utf8"));
|
|
144
|
+
const script = pkg.scripts?.["check"];
|
|
145
|
+
return typeof script === "string" && /(^|\s)checkride(\s|$)/.test(script);
|
|
146
|
+
} catch {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function checkride_detected(workspace) {
|
|
151
|
+
return existsSync(join(workspace, "checkride.config.json")) || package_check_script_is_checkride(workspace) || existsSync(join(workspace, "node_modules", ".bin", "checkride"));
|
|
152
|
+
}
|
|
153
|
+
function resolve_check_runner(check, workspace) {
|
|
154
|
+
if (check === "none") return "none";
|
|
155
|
+
if (check === "auto") {
|
|
156
|
+
return checkride_detected(workspace) ? "checkride" : "none";
|
|
157
|
+
}
|
|
158
|
+
return /(^|\s)checkride(\s|$)/.test(check.trim()) ? "checkride" : "command";
|
|
159
|
+
}
|
|
160
|
+
|
|
136
161
|
// src/types.ts
|
|
137
162
|
function config_error(message) {
|
|
138
163
|
return Object.assign(new Error(message), { kind: "config_error" });
|
|
@@ -192,17 +217,17 @@ var PERMISSION_MODES = [
|
|
|
192
217
|
function expand_at_file(value, cwd) {
|
|
193
218
|
if (!value.startsWith("@")) return value;
|
|
194
219
|
const path = resolve(cwd, value.slice(1));
|
|
195
|
-
if (!
|
|
220
|
+
if (!existsSync2(path)) {
|
|
196
221
|
throw config_error(`file not found: ${path} (from ${value})`);
|
|
197
222
|
}
|
|
198
|
-
return
|
|
223
|
+
return readFileSync2(path, "utf8");
|
|
199
224
|
}
|
|
200
225
|
function is_critic_preset(value) {
|
|
201
226
|
return CRITIC_PRESETS.includes(value);
|
|
202
227
|
}
|
|
203
228
|
async function load_config_file(path) {
|
|
204
229
|
const abs = resolve(path);
|
|
205
|
-
if (!
|
|
230
|
+
if (!existsSync2(abs)) {
|
|
206
231
|
throw config_error(`config file not found: ${abs}`);
|
|
207
232
|
}
|
|
208
233
|
let loaded;
|
|
@@ -220,7 +245,7 @@ async function load_config_file(path) {
|
|
|
220
245
|
}
|
|
221
246
|
function validate_workspace(workspace) {
|
|
222
247
|
const abs = isAbsolute(workspace) ? workspace : resolve(workspace);
|
|
223
|
-
if (!
|
|
248
|
+
if (!existsSync2(abs)) {
|
|
224
249
|
throw config_error(`workspace not found: ${abs}`);
|
|
225
250
|
}
|
|
226
251
|
if (!statSync(abs).isDirectory()) {
|
|
@@ -238,7 +263,7 @@ function resolve_critic(critic, cwd) {
|
|
|
238
263
|
return { critic_preset: critic, critic_prompt_path: null };
|
|
239
264
|
}
|
|
240
265
|
const path = resolve(cwd, critic);
|
|
241
|
-
if (!
|
|
266
|
+
if (!existsSync2(path)) {
|
|
242
267
|
throw config_error(
|
|
243
268
|
`--critic must be one of ${CRITIC_PRESETS.join(", ")} or a path to a prompt file; got: ${critic}`
|
|
244
269
|
);
|
|
@@ -282,6 +307,16 @@ function detect_containment(env) {
|
|
|
282
307
|
const value = env["VOLLEY_CONTAINED"];
|
|
283
308
|
return value === "1" || value === "true";
|
|
284
309
|
}
|
|
310
|
+
var RUN_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
311
|
+
function resolve_run_id(run_id) {
|
|
312
|
+
if (run_id === void 0) return randomUUID();
|
|
313
|
+
if (!RUN_ID_PATTERN.test(run_id)) {
|
|
314
|
+
throw config_error(
|
|
315
|
+
`--run-id must be letters, digits, '-', and '_', starting with a letter or digit; got: ${run_id}`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
return run_id;
|
|
319
|
+
}
|
|
285
320
|
function resolve_config(raw, options = {}) {
|
|
286
321
|
const cwd = options.cwd ?? process.cwd();
|
|
287
322
|
const env = options.env ?? process.env;
|
|
@@ -347,11 +382,11 @@ function resolve_config(raw, options = {}) {
|
|
|
347
382
|
);
|
|
348
383
|
}
|
|
349
384
|
}
|
|
350
|
-
if (raw.git_checkpoints === true && !
|
|
385
|
+
if (raw.git_checkpoints === true && !existsSync2(resolve(workspace, ".git"))) {
|
|
351
386
|
throw config_error(`--git requires the workspace to be a git repository: ${workspace}`);
|
|
352
387
|
}
|
|
353
388
|
const worktree = raw.worktree ?? false;
|
|
354
|
-
if (worktree && !
|
|
389
|
+
if (worktree && !existsSync2(resolve(workspace, ".git"))) {
|
|
355
390
|
throw config_error(`--worktree requires the workspace to be a git repository: ${workspace}`);
|
|
356
391
|
}
|
|
357
392
|
const discard_worktree = raw.discard_worktree ?? false;
|
|
@@ -365,14 +400,16 @@ function resolve_config(raw, options = {}) {
|
|
|
365
400
|
throw config_error("gate_paths must be an array of glob strings");
|
|
366
401
|
}
|
|
367
402
|
const sandbox_image = resolve_sandbox_image(raw, env);
|
|
403
|
+
const run_id = resolve_run_id(options.run_id);
|
|
404
|
+
const check = raw.check ?? DEFAULT_CHECK;
|
|
368
405
|
return {
|
|
369
406
|
version: 2,
|
|
370
|
-
run_id
|
|
407
|
+
run_id,
|
|
371
408
|
started_at: options.started_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
372
409
|
prompt,
|
|
373
410
|
criteria,
|
|
374
|
-
check
|
|
375
|
-
check_resolved:
|
|
411
|
+
check,
|
|
412
|
+
check_resolved: resolve_check_runner(check, workspace),
|
|
376
413
|
builder_model: raw.builder_model ?? DEFAULT_BUILDER_MODEL,
|
|
377
414
|
builder_provider,
|
|
378
415
|
builder_max_steps,
|
|
@@ -399,12 +436,12 @@ function resolve_config(raw, options = {}) {
|
|
|
399
436
|
}
|
|
400
437
|
|
|
401
438
|
// src/orchestrator.ts
|
|
402
|
-
import { existsSync as
|
|
439
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
|
|
403
440
|
import { run } from "fascicle";
|
|
404
441
|
import { filesystem_logger } from "fascicle/adapters";
|
|
405
442
|
|
|
406
443
|
// src/engine.ts
|
|
407
|
-
import { existsSync as
|
|
444
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
408
445
|
import { create_engine } from "fascicle";
|
|
409
446
|
var DEFAULT_OLLAMA_URL = "http://localhost:11434";
|
|
410
447
|
var DEFAULT_LMSTUDIO_URL = "http://localhost:1234/v1";
|
|
@@ -428,11 +465,11 @@ function resolve_ollama_base_url(env) {
|
|
|
428
465
|
return cross_to_host_gateway(normalized, env);
|
|
429
466
|
}
|
|
430
467
|
function load_pricing_overrides(path) {
|
|
431
|
-
if (!
|
|
468
|
+
if (!existsSync3(path)) {
|
|
432
469
|
throw config_error(`VOLLEY_PRICING_PATH not found: ${path}`);
|
|
433
470
|
}
|
|
434
471
|
try {
|
|
435
|
-
return JSON.parse(
|
|
472
|
+
return JSON.parse(readFileSync3(path, "utf8"));
|
|
436
473
|
} catch (err) {
|
|
437
474
|
const detail = err instanceof Error ? err.message : String(err);
|
|
438
475
|
throw config_error(`VOLLEY_PRICING_PATH is not valid JSON: ${detail}`);
|
|
@@ -483,8 +520,8 @@ function create_volley_engine(options) {
|
|
|
483
520
|
import { branch, fallback, loop, pipe, retry, sequence, step } from "fascicle";
|
|
484
521
|
|
|
485
522
|
// src/builder.ts
|
|
486
|
-
import { existsSync as
|
|
487
|
-
import { dirname as dirname2, join as
|
|
523
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
524
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
488
525
|
import { fileURLToPath } from "url";
|
|
489
526
|
import { model_call } from "fascicle";
|
|
490
527
|
|
|
@@ -528,7 +565,7 @@ async function warn_small_local_context(provider, model, base_url, warn, abort)
|
|
|
528
565
|
// src/builder/tools.ts
|
|
529
566
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
530
567
|
import { lookup as dns_lookup } from "dns";
|
|
531
|
-
import { mkdirSync, readFileSync as
|
|
568
|
+
import { mkdirSync, readFileSync as readFileSync5, writeFileSync } from "fs";
|
|
532
569
|
import { dirname } from "path";
|
|
533
570
|
import { Readability } from "@mozilla/readability";
|
|
534
571
|
import ipaddr from "ipaddr.js";
|
|
@@ -538,8 +575,8 @@ import { Agent, buildConnector, interceptors, request } from "undici";
|
|
|
538
575
|
import { z as z2 } from "zod";
|
|
539
576
|
|
|
540
577
|
// src/workspace_tools.ts
|
|
541
|
-
import { readFileSync as
|
|
542
|
-
import { isAbsolute as isAbsolute2, join, relative, resolve as resolve2, sep } from "path";
|
|
578
|
+
import { readFileSync as readFileSync4, readdirSync, statSync as statSync2 } from "fs";
|
|
579
|
+
import { isAbsolute as isAbsolute2, join as join2, relative, resolve as resolve2, sep } from "path";
|
|
543
580
|
import { z } from "zod";
|
|
544
581
|
var READ_FILE_MAX_BYTES = 2e5;
|
|
545
582
|
var SEARCH_MAX_MATCHES = 200;
|
|
@@ -573,7 +610,7 @@ function walk(root, start, limit) {
|
|
|
573
610
|
continue;
|
|
574
611
|
}
|
|
575
612
|
for (const name of entries) {
|
|
576
|
-
const abs =
|
|
613
|
+
const abs = join2(dir, name);
|
|
577
614
|
let is_dir;
|
|
578
615
|
try {
|
|
579
616
|
is_dir = statSync2(abs).isDirectory();
|
|
@@ -618,7 +655,7 @@ function read_only_tools(workspace) {
|
|
|
618
655
|
execute: (raw) => {
|
|
619
656
|
const input = read_file_input.parse(raw);
|
|
620
657
|
const abs = contain(workspace, input.path);
|
|
621
|
-
const content =
|
|
658
|
+
const content = readFileSync4(abs, "utf8");
|
|
622
659
|
return content.length > READ_FILE_MAX_BYTES ? `${content.slice(0, READ_FILE_MAX_BYTES)}
|
|
623
660
|
\u2026 [truncated at ${String(READ_FILE_MAX_BYTES)} bytes]` : content;
|
|
624
661
|
}
|
|
@@ -637,7 +674,7 @@ function read_only_tools(workspace) {
|
|
|
637
674
|
if (hits.length >= SEARCH_MAX_MATCHES) break;
|
|
638
675
|
let content;
|
|
639
676
|
try {
|
|
640
|
-
content =
|
|
677
|
+
content = readFileSync4(file.abs, "utf8");
|
|
641
678
|
} catch {
|
|
642
679
|
continue;
|
|
643
680
|
}
|
|
@@ -745,9 +782,9 @@ function is_forbidden_address(address) {
|
|
|
745
782
|
var default_resolver = (hostname, options, callback) => {
|
|
746
783
|
dns_lookup(hostname, options, callback);
|
|
747
784
|
};
|
|
748
|
-
function guarded_lookup(is_forbidden,
|
|
785
|
+
function guarded_lookup(is_forbidden, resolve5) {
|
|
749
786
|
return (hostname, options, callback) => {
|
|
750
|
-
|
|
787
|
+
resolve5(hostname, options, (err, address, family) => {
|
|
751
788
|
if (err !== null) {
|
|
752
789
|
callback(err, "", 0);
|
|
753
790
|
return;
|
|
@@ -769,8 +806,8 @@ function ssrf_error(address, hostname) {
|
|
|
769
806
|
err.code = "ERR_SSRF_BLOCKED";
|
|
770
807
|
return err;
|
|
771
808
|
}
|
|
772
|
-
function create_ssrf_agent(is_forbidden,
|
|
773
|
-
const base = buildConnector({ lookup: guarded_lookup(is_forbidden,
|
|
809
|
+
function create_ssrf_agent(is_forbidden, resolve5) {
|
|
810
|
+
const base = buildConnector({ lookup: guarded_lookup(is_forbidden, resolve5), timeout: FETCH_TIMEOUT_MS });
|
|
774
811
|
const connector = (options, callback) => {
|
|
775
812
|
base(options, (err, socket) => {
|
|
776
813
|
if (err !== null) {
|
|
@@ -980,7 +1017,7 @@ function builder_tools(workspace, options = {}) {
|
|
|
980
1017
|
execute: (raw) => {
|
|
981
1018
|
const input = edit_file_input.parse(raw);
|
|
982
1019
|
const abs = contain(workspace, input.path);
|
|
983
|
-
const content =
|
|
1020
|
+
const content = readFileSync5(abs, "utf8");
|
|
984
1021
|
const matches = count_occurrences(content, input.old_str);
|
|
985
1022
|
if (matches === 0) {
|
|
986
1023
|
return `error: old_str not found in ${input.path} (0 matches). No change made. Read the file and retry with an exact excerpt of its current content.`;
|
|
@@ -1120,16 +1157,16 @@ async function prewarm_ollama_model(base_url, model, abort) {
|
|
|
1120
1157
|
}
|
|
1121
1158
|
|
|
1122
1159
|
// src/worktree.ts
|
|
1123
|
-
import { existsSync as
|
|
1124
|
-
import { join as
|
|
1160
|
+
import { existsSync as existsSync5, renameSync as renameSync2, rmSync, symlinkSync } from "fs";
|
|
1161
|
+
import { join as join4, resolve as resolve3 } from "path";
|
|
1125
1162
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
1126
1163
|
|
|
1127
1164
|
// src/workspace.ts
|
|
1128
|
-
import { existsSync as
|
|
1129
|
-
import { join as
|
|
1165
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1166
|
+
import { join as join3 } from "path";
|
|
1130
1167
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
1131
1168
|
function volley_path(workspace, ...parts) {
|
|
1132
|
-
return
|
|
1169
|
+
return join3(workspace, ".volley", ...parts);
|
|
1133
1170
|
}
|
|
1134
1171
|
function iteration_dir(workspace, iteration) {
|
|
1135
1172
|
return volley_path(workspace, "iterations", String(iteration).padStart(3, "0"));
|
|
@@ -1139,8 +1176,8 @@ function backup_stamp(date = /* @__PURE__ */ new Date()) {
|
|
|
1139
1176
|
}
|
|
1140
1177
|
function initialize_workspace(workspace, options = {}) {
|
|
1141
1178
|
const dir = volley_path(workspace);
|
|
1142
|
-
if (
|
|
1143
|
-
renameSync(dir,
|
|
1179
|
+
if (existsSync4(dir) && options.preserve !== true) {
|
|
1180
|
+
renameSync(dir, join3(workspace, `.volley.bak.${backup_stamp()}`));
|
|
1144
1181
|
}
|
|
1145
1182
|
mkdirSync2(volley_path(workspace, "iterations"), { recursive: true });
|
|
1146
1183
|
}
|
|
@@ -1178,7 +1215,7 @@ function write_resolved_config(config) {
|
|
|
1178
1215
|
);
|
|
1179
1216
|
}
|
|
1180
1217
|
function git_checkpoint(root, message) {
|
|
1181
|
-
if (!
|
|
1218
|
+
if (!existsSync4(join3(root, ".git"))) {
|
|
1182
1219
|
throw config_error(`git checkpoints require a git repository: ${root}`);
|
|
1183
1220
|
}
|
|
1184
1221
|
const add = spawnSync3("git", ["add", "-A"], { cwd: root });
|
|
@@ -1197,7 +1234,7 @@ function git_checkpoint(root, message) {
|
|
|
1197
1234
|
}
|
|
1198
1235
|
}
|
|
1199
1236
|
function integrate_worktree(workspace, branch2, message) {
|
|
1200
|
-
if (!
|
|
1237
|
+
if (!existsSync4(join3(workspace, ".git"))) {
|
|
1201
1238
|
throw config_error(`--worktree integration requires a git repository: ${workspace}`);
|
|
1202
1239
|
}
|
|
1203
1240
|
const squash = spawnSync3("git", ["merge", "--squash", branch2], {
|
|
@@ -1299,14 +1336,14 @@ function branch_exists(repo, branch2) {
|
|
|
1299
1336
|
}
|
|
1300
1337
|
function create_worktree(options) {
|
|
1301
1338
|
const repo = resolve3(options.workspace);
|
|
1302
|
-
if (!
|
|
1339
|
+
if (!existsSync5(join4(repo, ".git"))) {
|
|
1303
1340
|
throw config_error(`--worktree requires the workspace to be a git repository: ${repo}`);
|
|
1304
1341
|
}
|
|
1305
1342
|
const path = worktree_path(repo);
|
|
1306
1343
|
const base = options.base ?? "HEAD";
|
|
1307
1344
|
const log = options.log ?? (() => {
|
|
1308
1345
|
});
|
|
1309
|
-
if (
|
|
1346
|
+
if (existsSync5(path)) {
|
|
1310
1347
|
const backup = `${path}.bak.${backup_stamp()}`;
|
|
1311
1348
|
renameSync2(path, backup);
|
|
1312
1349
|
git2(repo, ["worktree", "prune"]);
|
|
@@ -1318,9 +1355,9 @@ function create_worktree(options) {
|
|
|
1318
1355
|
log(`worktree: rotated existing branch ${options.branch} aside to ${backup_branch}`);
|
|
1319
1356
|
}
|
|
1320
1357
|
git_ok(repo, ["worktree", "add", "-b", options.branch, path, base]);
|
|
1321
|
-
const modules =
|
|
1322
|
-
if (
|
|
1323
|
-
symlinkSync(modules,
|
|
1358
|
+
const modules = join4(repo, "node_modules");
|
|
1359
|
+
if (existsSync5(modules) && !existsSync5(join4(path, "node_modules"))) {
|
|
1360
|
+
symlinkSync(modules, join4(path, "node_modules"), "dir");
|
|
1324
1361
|
log(`worktree: linked node_modules from ${repo}`);
|
|
1325
1362
|
}
|
|
1326
1363
|
return { path, branch: options.branch };
|
|
@@ -1335,7 +1372,7 @@ function teardown_worktree(options) {
|
|
|
1335
1372
|
git_quiet(repo, ["branch", "-D", options.branch]);
|
|
1336
1373
|
}
|
|
1337
1374
|
git_quiet(repo, ["worktree", "prune"]);
|
|
1338
|
-
if (
|
|
1375
|
+
if (existsSync5(path)) {
|
|
1339
1376
|
try {
|
|
1340
1377
|
rmSync(path, { recursive: true, force: true });
|
|
1341
1378
|
} catch {
|
|
@@ -1344,7 +1381,7 @@ function teardown_worktree(options) {
|
|
|
1344
1381
|
return outcome;
|
|
1345
1382
|
}
|
|
1346
1383
|
function salvage_worktree(repo, path, branch2) {
|
|
1347
|
-
if (
|
|
1384
|
+
if (existsSync5(path)) {
|
|
1348
1385
|
git_quiet(path, ["add", "-A"]);
|
|
1349
1386
|
git_quiet(path, [
|
|
1350
1387
|
"commit",
|
|
@@ -1417,8 +1454,8 @@ function compose_builder_system() {
|
|
|
1417
1454
|
function presets_dir() {
|
|
1418
1455
|
let dir = dirname2(fileURLToPath(import.meta.url));
|
|
1419
1456
|
for (let depth = 0; depth < 6; depth += 1) {
|
|
1420
|
-
const candidate =
|
|
1421
|
-
if (
|
|
1457
|
+
const candidate = join5(dir, "src", "builder", "presets");
|
|
1458
|
+
if (existsSync6(candidate)) return candidate;
|
|
1422
1459
|
dir = dirname2(dir);
|
|
1423
1460
|
}
|
|
1424
1461
|
throw config_error("builder presets directory not found");
|
|
@@ -1429,7 +1466,7 @@ function compose_builder_system_local() {
|
|
|
1429
1466
|
"iterating on a workspace until it satisfies a task and its acceptance",
|
|
1430
1467
|
"criteria."
|
|
1431
1468
|
].join("\n");
|
|
1432
|
-
const append =
|
|
1469
|
+
const append = readFileSync6(join5(presets_dir(), "harness_append_local.md"), "utf8");
|
|
1433
1470
|
return `${identity}
|
|
1434
1471
|
|
|
1435
1472
|
${append.trimEnd()}`;
|
|
@@ -1552,24 +1589,24 @@ import { z as z3 } from "zod";
|
|
|
1552
1589
|
import { model_call as model_call2 } from "fascicle";
|
|
1553
1590
|
|
|
1554
1591
|
// src/critic/prompt.ts
|
|
1555
|
-
import { existsSync as
|
|
1556
|
-
import { dirname as dirname3, join as
|
|
1592
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
1593
|
+
import { dirname as dirname3, join as join6 } from "path";
|
|
1557
1594
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1558
1595
|
function presets_dir2() {
|
|
1559
1596
|
let dir = dirname3(fileURLToPath2(import.meta.url));
|
|
1560
1597
|
for (let depth = 0; depth < 6; depth += 1) {
|
|
1561
|
-
const candidate =
|
|
1562
|
-
if (
|
|
1563
|
-
const local =
|
|
1564
|
-
if (
|
|
1598
|
+
const candidate = join6(dir, "src", "critic", "presets");
|
|
1599
|
+
if (existsSync7(candidate)) return candidate;
|
|
1600
|
+
const local = join6(dir, "presets");
|
|
1601
|
+
if (existsSync7(local)) return local;
|
|
1565
1602
|
dir = dirname3(dir);
|
|
1566
1603
|
}
|
|
1567
1604
|
throw config_error("critic presets directory not found");
|
|
1568
1605
|
}
|
|
1569
1606
|
function resolve_critic_prompt(config) {
|
|
1570
|
-
const base = config.critic_prompt_path !== null ?
|
|
1607
|
+
const base = config.critic_prompt_path !== null ? readFileSync7(config.critic_prompt_path, "utf8") : readFileSync7(join6(presets_dir2(), `${config.critic_preset}.md`), "utf8");
|
|
1571
1608
|
const append_file = config.critic_provider === "claude_cli" ? "harness_append.md" : "harness_append_local.md";
|
|
1572
|
-
const append =
|
|
1609
|
+
const append = readFileSync7(join6(presets_dir2(), append_file), "utf8");
|
|
1573
1610
|
return `${base.trimEnd()}
|
|
1574
1611
|
|
|
1575
1612
|
${append.trimEnd()}`;
|
|
@@ -1869,8 +1906,8 @@ function status_of(value, converged) {
|
|
|
1869
1906
|
}
|
|
1870
1907
|
|
|
1871
1908
|
// src/check/checkride.ts
|
|
1872
|
-
import { existsSync as
|
|
1873
|
-
import { join as
|
|
1909
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
1910
|
+
import { join as join7 } from "path";
|
|
1874
1911
|
import { spawn } from "child_process";
|
|
1875
1912
|
import { aborted_error } from "fascicle";
|
|
1876
1913
|
var CHECK_ARTIFACT_MAX_CHARS = 8e3;
|
|
@@ -1922,9 +1959,9 @@ function parse_summary_with_fallback(stdout, workspace) {
|
|
|
1922
1959
|
try {
|
|
1923
1960
|
return parse_checkride_summary(stdout);
|
|
1924
1961
|
} catch (err) {
|
|
1925
|
-
const file =
|
|
1926
|
-
if (!
|
|
1927
|
-
return parse_checkride_summary(
|
|
1962
|
+
const file = join7(workspace, ".check", "summary.json");
|
|
1963
|
+
if (!existsSync8(file)) throw err;
|
|
1964
|
+
return parse_checkride_summary(readFileSync8(file, "utf8"));
|
|
1928
1965
|
}
|
|
1929
1966
|
}
|
|
1930
1967
|
function failing_checks(summary) {
|
|
@@ -1933,11 +1970,11 @@ function failing_checks(summary) {
|
|
|
1933
1970
|
function read_failing_artifacts(workspace, failing) {
|
|
1934
1971
|
return failing.map((check) => {
|
|
1935
1972
|
const file = check.output_file ?? `${check.name}.stdout.txt`;
|
|
1936
|
-
const path =
|
|
1937
|
-
if (!
|
|
1973
|
+
const path = join7(workspace, ".check", file);
|
|
1974
|
+
if (!existsSync8(path)) {
|
|
1938
1975
|
return { slot: check.name, path: null, content: "(no artifact found)", truncated: false };
|
|
1939
1976
|
}
|
|
1940
|
-
const raw =
|
|
1977
|
+
const raw = readFileSync8(path, "utf8");
|
|
1941
1978
|
const truncated = raw.length > CHECK_ARTIFACT_MAX_CHARS;
|
|
1942
1979
|
return {
|
|
1943
1980
|
slot: check.name,
|
|
@@ -2009,14 +2046,14 @@ function skipped_check(reason) {
|
|
|
2009
2046
|
|
|
2010
2047
|
// src/iteration.ts
|
|
2011
2048
|
import {
|
|
2012
|
-
existsSync as
|
|
2049
|
+
existsSync as existsSync9,
|
|
2013
2050
|
mkdirSync as mkdirSync3,
|
|
2014
|
-
readFileSync as
|
|
2051
|
+
readFileSync as readFileSync9,
|
|
2015
2052
|
readdirSync as readdirSync2,
|
|
2016
2053
|
rmSync as rmSync2,
|
|
2017
2054
|
writeFileSync as writeFileSync3
|
|
2018
2055
|
} from "fs";
|
|
2019
|
-
import { basename, join as
|
|
2056
|
+
import { basename, join as join8 } from "path";
|
|
2020
2057
|
function write_json(path, value) {
|
|
2021
2058
|
writeFileSync3(path, `${JSON.stringify(value, null, 2)}
|
|
2022
2059
|
`);
|
|
@@ -2060,32 +2097,32 @@ function phase_summary(record) {
|
|
|
2060
2097
|
}
|
|
2061
2098
|
function archive_iteration(config, state) {
|
|
2062
2099
|
const dir = iteration_dir(config.workspace, state.iteration);
|
|
2063
|
-
mkdirSync3(
|
|
2100
|
+
mkdirSync3(join8(dir, "check"), { recursive: true });
|
|
2064
2101
|
if (state.verdict !== null && state.feedback !== null) {
|
|
2065
2102
|
persist_verdict_files(config.workspace, state.feedback, state.verdict);
|
|
2066
|
-
writeFileSync3(
|
|
2067
|
-
writeFileSync3(
|
|
2103
|
+
writeFileSync3(join8(dir, "feedback.md"), readFileSync9(volley_path(config.workspace, "feedback.md")));
|
|
2104
|
+
writeFileSync3(join8(dir, "verdict"), `${state.verdict}
|
|
2068
2105
|
`);
|
|
2069
2106
|
}
|
|
2070
2107
|
const check = state.check;
|
|
2071
2108
|
if (check !== null && check.ran) {
|
|
2072
2109
|
if (check.runner === "checkride") {
|
|
2073
2110
|
if (check.summary !== void 0) {
|
|
2074
|
-
write_json(
|
|
2111
|
+
write_json(join8(dir, "check", "summary.json"), check.summary);
|
|
2075
2112
|
}
|
|
2076
2113
|
for (const artifact of check.detail) {
|
|
2077
|
-
if (artifact.path !== null &&
|
|
2078
|
-
writeFileSync3(
|
|
2114
|
+
if (artifact.path !== null && existsSync9(artifact.path)) {
|
|
2115
|
+
writeFileSync3(join8(dir, "check", basename(artifact.path)), readFileSync9(artifact.path));
|
|
2079
2116
|
}
|
|
2080
2117
|
}
|
|
2081
2118
|
} else {
|
|
2082
|
-
writeFileSync3(
|
|
2083
|
-
writeFileSync3(
|
|
2119
|
+
writeFileSync3(join8(dir, "check", "check.log"), check.log ?? "");
|
|
2120
|
+
writeFileSync3(join8(dir, "check", "check.exit"), `${String(check.exit_code)}
|
|
2084
2121
|
`);
|
|
2085
2122
|
}
|
|
2086
2123
|
}
|
|
2087
2124
|
const completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
2088
|
-
write_json(
|
|
2125
|
+
write_json(join8(dir, "summary.json"), {
|
|
2089
2126
|
iteration: state.iteration,
|
|
2090
2127
|
started_at: state.iteration_started_at,
|
|
2091
2128
|
completed_at,
|
|
@@ -2135,20 +2172,20 @@ function run_result_from_state(config, state, status, completed_at) {
|
|
|
2135
2172
|
}
|
|
2136
2173
|
|
|
2137
2174
|
// src/summary.ts
|
|
2138
|
-
import { existsSync as
|
|
2175
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10, readdirSync as readdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
2139
2176
|
function transport_of(provider) {
|
|
2140
2177
|
return provider === "claude_cli" ? "claude_cli" : "ai_sdk";
|
|
2141
2178
|
}
|
|
2142
2179
|
function read_iteration_archives(workspace) {
|
|
2143
2180
|
const root = volley_path(workspace, "iterations");
|
|
2144
|
-
if (!
|
|
2181
|
+
if (!existsSync10(root)) return [];
|
|
2145
2182
|
const dirs = readdirSync3(root).filter((dir) => /^\d{3}$/.test(dir)).toSorted();
|
|
2146
2183
|
const archives = [];
|
|
2147
2184
|
for (const dir of dirs) {
|
|
2148
2185
|
const path = volley_path(workspace, "iterations", dir, "summary.json");
|
|
2149
|
-
if (!
|
|
2186
|
+
if (!existsSync10(path)) continue;
|
|
2150
2187
|
try {
|
|
2151
|
-
archives.push(JSON.parse(
|
|
2188
|
+
archives.push(JSON.parse(readFileSync10(path, "utf8")));
|
|
2152
2189
|
} catch {
|
|
2153
2190
|
}
|
|
2154
2191
|
}
|
|
@@ -2501,7 +2538,7 @@ function finalize_failed_run(config, err) {
|
|
|
2501
2538
|
const status = kind === "aborted_error" || cause === "aborted_error" ? "interrupted" : "error";
|
|
2502
2539
|
try {
|
|
2503
2540
|
const summary_path = volley_path(config.workspace, "summary.json");
|
|
2504
|
-
const prior =
|
|
2541
|
+
const prior = existsSync11(summary_path) ? JSON.parse(readFileSync11(summary_path, "utf8")) : run_result_from_state(config, initial_state(), status, null);
|
|
2505
2542
|
write_run_summary(config, {
|
|
2506
2543
|
...prior,
|
|
2507
2544
|
status,
|
|
@@ -2679,6 +2716,82 @@ function summarize_output(output) {
|
|
|
2679
2716
|
}
|
|
2680
2717
|
}
|
|
2681
2718
|
|
|
2719
|
+
// src/sandbox.ts
|
|
2720
|
+
import { resolve as resolve4 } from "path";
|
|
2721
|
+
var SANDBOX_WORKDIR = "/workspace";
|
|
2722
|
+
var SANDBOX_HOME = "/home/node";
|
|
2723
|
+
var HOST_GATEWAY_HOST = "host.docker.internal";
|
|
2724
|
+
var SANDBOX_NETWORK_NAME = "volley-sandbox-net";
|
|
2725
|
+
var SANDBOX_NETWORK_SUBNET = "172.31.99.0/24";
|
|
2726
|
+
function network_run_args(network, network_name) {
|
|
2727
|
+
if (network === "none") return ["--network", "none"];
|
|
2728
|
+
return ["--network", network_name, "--add-host", `${HOST_GATEWAY_HOST}:host-gateway`];
|
|
2729
|
+
}
|
|
2730
|
+
function sandbox_run_args(options) {
|
|
2731
|
+
if (options.uid === 0) {
|
|
2732
|
+
throw config_error("refusing to start the builder sandbox as root (uid 0)");
|
|
2733
|
+
}
|
|
2734
|
+
const user = options.uid !== null && options.gid !== null ? ["--user", `${String(options.uid)}:${String(options.gid)}`] : [];
|
|
2735
|
+
const name = options.name !== void 0 && options.name !== null ? ["--name", options.name] : [];
|
|
2736
|
+
const entrypoint = options.entrypoint !== void 0 && options.entrypoint !== null ? ["--entrypoint", options.entrypoint] : [];
|
|
2737
|
+
const env = (options.env ?? []).flatMap(([key, value]) => ["-e", `${key}=${value}`]);
|
|
2738
|
+
const labels = (options.labels ?? []).flatMap(([key, value]) => ["--label", `${key}=${value}`]);
|
|
2739
|
+
const store = options.store_volume !== void 0 && options.store_volume !== null ? ["-v", `${options.store_volume}:${SANDBOX_HOME}/.local/share/pnpm/store`] : [];
|
|
2740
|
+
return [
|
|
2741
|
+
"run",
|
|
2742
|
+
// One-shot, foreground container: the whole run *is* this container, so
|
|
2743
|
+
// remove it when volley exits.
|
|
2744
|
+
"--rm",
|
|
2745
|
+
...name,
|
|
2746
|
+
...labels,
|
|
2747
|
+
...user,
|
|
2748
|
+
// Network policy: default-deny egress. `'none'` gives the container
|
|
2749
|
+
// no interface; `'allowlist'` puts it on the user-defined bridge and collapses
|
|
2750
|
+
// the allowlist targets onto the host gateway.
|
|
2751
|
+
...network_run_args(options.network, options.network_name),
|
|
2752
|
+
// Resource caps: contain a runaway / fork-bomb / OOM without starving
|
|
2753
|
+
// pnpm/tsc (pids 1024, not 100; nofile high enough for esbuild/watchers).
|
|
2754
|
+
"--memory=4g",
|
|
2755
|
+
"--memory-swap=4g",
|
|
2756
|
+
"--memory-swappiness=0",
|
|
2757
|
+
"--cpus=2",
|
|
2758
|
+
"--pids-limit=1024",
|
|
2759
|
+
"--ulimit",
|
|
2760
|
+
"nofile=8192:16384",
|
|
2761
|
+
"--ulimit",
|
|
2762
|
+
"core=0",
|
|
2763
|
+
// Privilege caps: drop every capability, forbid privilege
|
|
2764
|
+
// escalation, keep the default seccomp profile, and run tini (PID 1) to reap
|
|
2765
|
+
// zombies. Never `--privileged`.
|
|
2766
|
+
"--cap-drop=ALL",
|
|
2767
|
+
"--security-opt",
|
|
2768
|
+
"no-new-privileges",
|
|
2769
|
+
"--init",
|
|
2770
|
+
// Read-only rootfs with tmpfs for the few writable paths the toolchain
|
|
2771
|
+
// needs; a pnpm store, when mounted, persists on a named volume.
|
|
2772
|
+
"--read-only",
|
|
2773
|
+
"--tmpfs",
|
|
2774
|
+
"/tmp",
|
|
2775
|
+
"--tmpfs",
|
|
2776
|
+
"/run",
|
|
2777
|
+
"--tmpfs",
|
|
2778
|
+
`${SANDBOX_HOME}/.cache`,
|
|
2779
|
+
"-e",
|
|
2780
|
+
`HOME=${SANDBOX_HOME}`,
|
|
2781
|
+
...env,
|
|
2782
|
+
// The bind-mounted worktree (writable — a bind mount is exempt from
|
|
2783
|
+
// `--read-only`) and, when one is named, the persistent pnpm store.
|
|
2784
|
+
"-v",
|
|
2785
|
+
`${resolve4(options.build_root)}:${SANDBOX_WORKDIR}`,
|
|
2786
|
+
...store,
|
|
2787
|
+
"-w",
|
|
2788
|
+
SANDBOX_WORKDIR,
|
|
2789
|
+
...entrypoint,
|
|
2790
|
+
options.image,
|
|
2791
|
+
...options.command ?? []
|
|
2792
|
+
];
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2682
2795
|
// src/exit_codes.ts
|
|
2683
2796
|
var EXIT_SUCCESS = 0;
|
|
2684
2797
|
var EXIT_BUDGET_EXHAUSTED = 2;
|
|
@@ -2745,6 +2858,8 @@ export {
|
|
|
2745
2858
|
EXIT_CRITIC_ERROR,
|
|
2746
2859
|
EXIT_INTERRUPTED,
|
|
2747
2860
|
EXIT_SUCCESS,
|
|
2861
|
+
SANDBOX_NETWORK_NAME,
|
|
2862
|
+
SANDBOX_NETWORK_SUBNET,
|
|
2748
2863
|
create_renderer,
|
|
2749
2864
|
exit_code_for_error,
|
|
2750
2865
|
exit_code_for_status,
|
|
@@ -2752,8 +2867,10 @@ export {
|
|
|
2752
2867
|
gate,
|
|
2753
2868
|
initial_state,
|
|
2754
2869
|
load_config_file,
|
|
2870
|
+
network_run_args,
|
|
2755
2871
|
resolve_config,
|
|
2756
2872
|
run_volley,
|
|
2873
|
+
sandbox_run_args,
|
|
2757
2874
|
status_of,
|
|
2758
2875
|
verdict_schema
|
|
2759
2876
|
};
|