@robmclarty/volley 0.8.0 → 0.10.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 +44 -0
- package/README.md +194 -45
- package/dist/cli.js +1284 -457
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +136 -12
- package/dist/index.js +1162 -543
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/builder/presets/harness_append_acp.md +4 -3
- package/src/builder/presets/harness_append_local.md +5 -3
- package/src/critic/presets/harness_append_local.md +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -32,7 +32,13 @@ type RunStatus = 'running' | 'success' | 'budget_exhausted' | 'cost_cap_reached'
|
|
|
32
32
|
/** The builder edited the gate that judges it, under `--fail-on-gate-edit`:
|
|
33
33
|
* the run halts on the spot rather than iterating, because a check the builder
|
|
34
34
|
* can rewrite is not evidence the work is done. */
|
|
35
|
-
'gate_edit_blocked' |
|
|
35
|
+
'gate_edit_blocked' |
|
|
36
|
+
/** A critique's other outcome: the critic requested changes, or the check
|
|
37
|
+
* failed, on the one change it reviewed. A run never ends this way. */
|
|
38
|
+
'rejected' | 'interrupted' | 'error';
|
|
39
|
+
/** What a run does: the builder/critic loop (`run`), or one check and one
|
|
40
|
+
* critique of a change already made, with no builder at all (`critique`). */
|
|
41
|
+
type RunMode = 'run' | 'critique';
|
|
36
42
|
/** User-facing configuration, before resolution (config file / flags). */
|
|
37
43
|
type VolleyConfig = {
|
|
38
44
|
prompt: string;
|
|
@@ -43,6 +49,8 @@ type VolleyConfig = {
|
|
|
43
49
|
builder_provider?: BuilderProvider;
|
|
44
50
|
builder_url?: string;
|
|
45
51
|
builder_max_steps?: number;
|
|
52
|
+
builder_max_tool_calls?: number;
|
|
53
|
+
builder_max_output_tokens?: number;
|
|
46
54
|
allow_unsandboxed_builder?: boolean;
|
|
47
55
|
critic_model?: string;
|
|
48
56
|
critic_provider?: CriticProvider;
|
|
@@ -69,6 +77,13 @@ type ResolvedConfig = {
|
|
|
69
77
|
version: 2;
|
|
70
78
|
run_id: string;
|
|
71
79
|
started_at: string;
|
|
80
|
+
/** A run, or a critique (`volley critique`). A critique's builder fields
|
|
81
|
+
* hold the defaults, untouched by flags and the environment, since no
|
|
82
|
+
* builder is ever constructed, and its loop settings are one round with
|
|
83
|
+
* nothing isolated or committed. */
|
|
84
|
+
mode: RunMode;
|
|
85
|
+
/** The task. A critique needs none, since a critic judges against the
|
|
86
|
+
* criteria alone, so it's empty unless one was given to record. */
|
|
72
87
|
prompt: string;
|
|
73
88
|
criteria: string;
|
|
74
89
|
check: CheckMode;
|
|
@@ -84,6 +99,21 @@ type ResolvedConfig = {
|
|
|
84
99
|
* lmstudio, openrouter) takes one. */
|
|
85
100
|
builder_url: string | null;
|
|
86
101
|
builder_max_steps: number;
|
|
102
|
+
/** How many tool calls a tool-loop builder may make in one turn, reads
|
|
103
|
+
* aside (`read_file`, `search_files`, `list_files` are `read_only` and
|
|
104
|
+
* don't count); fascicle runs a turn's calls in order up to the first one
|
|
105
|
+
* past the cap, drops the rest, and tells the model which it dropped. 1 by
|
|
106
|
+
* default, since some model and runtime pairs return parallel calls badly;
|
|
107
|
+
* raise it for a pair that handles them. Ignored by the `claude_cli`
|
|
108
|
+
* builder. */
|
|
109
|
+
builder_max_tool_calls: number;
|
|
110
|
+
/** The most output tokens a tool-loop builder may generate in one turn,
|
|
111
|
+
* reasoning included, or null to leave each turn to the provider's own
|
|
112
|
+
* limit. 16384 by default on a local builder (ollama, lmstudio), null on
|
|
113
|
+
* openrouter. A turn cut off at it before it made a tool call costs that
|
|
114
|
+
* one step, not the session: fascicle feeds back that it hit the limit and
|
|
115
|
+
* the loop goes on. Ignored by the `claude_cli` builder. */
|
|
116
|
+
builder_max_output_tokens: number | null;
|
|
87
117
|
/** Operator opt-out permitting a non-`claude_cli` builder to run without a
|
|
88
118
|
* sandbox — it gets a real host bash. Refused by default. */
|
|
89
119
|
allow_unsandboxed_builder: boolean;
|
|
@@ -127,9 +157,12 @@ type ResolvedConfig = {
|
|
|
127
157
|
* `VOLLEY_SANDBOX_IMAGE` override it. Meaningful only for a local builder —
|
|
128
158
|
* the `claude_cli` path never uses Docker. */
|
|
129
159
|
sandbox_image: string;
|
|
130
|
-
/** The root that holds each run's state at `<state_dir>/<run_id
|
|
131
|
-
*
|
|
132
|
-
*
|
|
160
|
+
/** The root that holds each run's state at `<state_dir>/<run_id>/`, as an
|
|
161
|
+
* absolute path: `--state-dir`, else the default root,
|
|
162
|
+
* `$XDG_STATE_HOME/volley` or `~/.local/state/volley`. `resolve_config`
|
|
163
|
+
* always sets one. Null is the layout older runs used, `<workspace>/.volley/`,
|
|
164
|
+
* rotated aside by the next fresh run: a run an older volley started
|
|
165
|
+
* resumes in it, and a hand-built config can still ask for it. */
|
|
133
166
|
state_dir: string | null;
|
|
134
167
|
workspace: string;
|
|
135
168
|
verbose: boolean;
|
|
@@ -154,6 +187,9 @@ type CheckResult = {
|
|
|
154
187
|
detail: CheckArtifact[];
|
|
155
188
|
/** Raw checkride summary JSON (when the runner is checkride). */
|
|
156
189
|
summary?: unknown;
|
|
190
|
+
/** checkride's capped excerpt of the failing slots (`.check/digest.md`),
|
|
191
|
+
* when the check failed and checkride wrote one. */
|
|
192
|
+
digest?: string;
|
|
157
193
|
/** Captured combined output (when the runner is a shell command). */
|
|
158
194
|
log?: string;
|
|
159
195
|
};
|
|
@@ -210,6 +246,10 @@ type PhaseRecord = {
|
|
|
210
246
|
* counts behind the salvage-rate health metric. */
|
|
211
247
|
tool_calls: number;
|
|
212
248
|
salvaged_tool_calls: number;
|
|
249
|
+
/** Turns the provider cut off at the output cap before they made a tool
|
|
250
|
+
* call (`--builder-max-output-tokens`), each of which cost a step and went
|
|
251
|
+
* on. Set on a builder record when any was; absent otherwise. Additive. */
|
|
252
|
+
truncated_turns?: number;
|
|
213
253
|
/** Provider-error retries that preceded this phase's successful call
|
|
214
254
|
* — the local-critic degradation ladder's first rung: a stochastic
|
|
215
255
|
* Ollama stream death is retried once before falling back. Set on the critic
|
|
@@ -266,11 +306,14 @@ type LoopState = {
|
|
|
266
306
|
builder_cost_usd: number;
|
|
267
307
|
critic_cost_usd: number;
|
|
268
308
|
check_duration_ms: number;
|
|
309
|
+
/** How many of the run's builder sessions ended on the step limit
|
|
310
|
+
* (`max_steps`) rather than calling finish. */
|
|
311
|
+
builder_max_steps_hits: number;
|
|
269
312
|
iteration_cost_usd: number;
|
|
270
313
|
halt: HaltReason;
|
|
271
314
|
cost_warned: boolean;
|
|
272
315
|
};
|
|
273
|
-
/** Projection of final loop state;
|
|
316
|
+
/** Projection of final loop state; the run's `summary.json` mirrors this. */
|
|
274
317
|
type RunResult = {
|
|
275
318
|
run_id: string;
|
|
276
319
|
status: RunStatus;
|
|
@@ -282,14 +325,27 @@ type RunResult = {
|
|
|
282
325
|
builder_cost_usd: number;
|
|
283
326
|
critic_cost_usd: number;
|
|
284
327
|
check_duration_ms: number;
|
|
328
|
+
/** How many of the run's builder sessions hit the step limit before they
|
|
329
|
+
* called finish: work handed to the check and critic unverified. The
|
|
330
|
+
* warning a run prints for each goes to stderr, which `--json` silences, so
|
|
331
|
+
* a harness reads it here. Always 0 for a `claude_cli` builder, which has
|
|
332
|
+
* no step limit, and for a critique. */
|
|
333
|
+
builder_max_steps_hits: number;
|
|
285
334
|
final_verdict: Verdict | null;
|
|
286
335
|
/** The run branch teardown left behind because this run's work was never
|
|
287
336
|
* integrated: under `--worktree` without `--git` a converged run has no
|
|
288
337
|
* other copy of its effects, so teardown commits them onto `volley/<run id>`
|
|
289
338
|
* and keeps the branch instead of force-deleting it. Null whenever nothing
|
|
290
339
|
* survived — every non-worktree run, every integrated run, every discarded
|
|
291
|
-
* one. `--json` consumers read it here;
|
|
340
|
+
* one. `--json` consumers read it here; the run's `summary.json` mirrors it. */
|
|
292
341
|
salvaged_branch: string | null;
|
|
342
|
+
/** A run, or a critique, which runs no builder. */
|
|
343
|
+
mode: RunMode;
|
|
344
|
+
/** Where this run keeps its config, summary, trajectory, feedback, verdict,
|
|
345
|
+
* and iterations: `<state_dir>/<run_id>/` under a state root, else the
|
|
346
|
+
* workspace's `.volley/`. Inside the sandbox container it is the container's
|
|
347
|
+
* path, which a harness maps back to the directory it mounted. */
|
|
348
|
+
run_state_dir: string;
|
|
293
349
|
};
|
|
294
350
|
//#endregion
|
|
295
351
|
//#region src/config.d.ts
|
|
@@ -307,6 +363,12 @@ type ResolveOptions = {
|
|
|
307
363
|
* while under `volley acp` each of its commands `ask`s a person first, which
|
|
308
364
|
* takes the container's place. */
|
|
309
365
|
uncontained_builder?: 'refuse' | 'ask';
|
|
366
|
+
/** Where the run's state goes, in place of what the config says: `volley
|
|
367
|
+
* resume` passes the root it found the run under, or null for a run an older
|
|
368
|
+
* volley kept in the workspace's `.volley/`, which resumes there. */
|
|
369
|
+
state_dir?: string | null;
|
|
370
|
+
/** A run (the default), or a critique, which has no builder or loop. */
|
|
371
|
+
mode?: RunMode;
|
|
310
372
|
};
|
|
311
373
|
/** Merge, expand, and validate a raw `VolleyConfig` into a `ResolvedConfig`.
|
|
312
374
|
* `check_resolved` comes from check detection against the workspace, so the
|
|
@@ -365,13 +427,19 @@ type BuilderDeps = {
|
|
|
365
427
|
};
|
|
366
428
|
//#endregion
|
|
367
429
|
//#region src/critic/run.d.ts
|
|
430
|
+
/** A critic that holds tools isn't constrain-decoded, so nothing stops it
|
|
431
|
+
* answering null for the feedback or the unmet list, as GLM did on approving.
|
|
432
|
+
* Both fields allow null and read it as empty, so that answer is a verdict
|
|
433
|
+
* rather than a schema failure that ends the run. fascicle sends the schema's
|
|
434
|
+
* input side, so the model sees each field as its type or null, still
|
|
435
|
+
* required; `.describe()` comes last so the description stays on the field. */
|
|
368
436
|
export declare const verdict_schema: z.ZodObject<{
|
|
369
437
|
verdict: z.ZodEnum<{
|
|
370
438
|
approved: "approved";
|
|
371
439
|
changes_requested: "changes_requested";
|
|
372
440
|
}>;
|
|
373
|
-
feedback: z.ZodString
|
|
374
|
-
unmet_criteria: z.ZodArray<z.ZodString
|
|
441
|
+
feedback: z.ZodPipe<z.ZodNullable<z.ZodString>, z.ZodTransform<string, string | null>>;
|
|
442
|
+
unmet_criteria: z.ZodPipe<z.ZodNullable<z.ZodArray<z.ZodString>>, z.ZodTransform<string[], string[] | null>>;
|
|
375
443
|
}, z.core.$strip>;
|
|
376
444
|
type VerdictOutput = z.infer<typeof verdict_schema>;
|
|
377
445
|
type CriticDeps = {
|
|
@@ -399,7 +467,7 @@ type RendererOptions = {
|
|
|
399
467
|
color: boolean;
|
|
400
468
|
max_cost_usd: number | null;
|
|
401
469
|
/** Where the run's trajectory is written, named when verbose output is cut
|
|
402
|
-
* short (default:
|
|
470
|
+
* short (default: "the run's trajectory.jsonl"). */
|
|
403
471
|
trajectory_path?: string;
|
|
404
472
|
write?: (text: string) => void;
|
|
405
473
|
};
|
|
@@ -467,6 +535,25 @@ type OrchestratorDeps = {
|
|
|
467
535
|
install_signal_handlers?: boolean;
|
|
468
536
|
};
|
|
469
537
|
export declare function run_volley(config: ResolvedConfig, deps: OrchestratorDeps, resume_from?: LoopState | null): Promise<RunResult>;
|
|
538
|
+
/** What a critique runs with: what a run does, less the builder's seat, plus
|
|
539
|
+
* the revision its change is measured from. */
|
|
540
|
+
type CritiqueDeps = Omit<OrchestratorDeps, 'seats'> & {
|
|
541
|
+
/** The revision the change under review is measured from (`--baseline`):
|
|
542
|
+
* everything that differs from it, committed or not, untracked files
|
|
543
|
+
* included. HEAD when absent, so the uncommitted edits are the change. */
|
|
544
|
+
baseline?: string;
|
|
545
|
+
/** The critic's tools in place of volley's reads, as for a run's critic. */
|
|
546
|
+
critic?: Omit<CriticSeat, 'engine'>;
|
|
547
|
+
};
|
|
548
|
+
/**
|
|
549
|
+
* `volley critique`: the check and the critic, once, over a change already
|
|
550
|
+
* made. No builder is constructed, and the engine serves the critic's seat
|
|
551
|
+
* alone, so a critic can be measured on fixed, labeled changes without paying
|
|
552
|
+
* for builder turns. It writes the state a one-round run does, with the mode
|
|
553
|
+
* recorded, and ends `success` on approval over a green check, else
|
|
554
|
+
* `rejected`.
|
|
555
|
+
*/
|
|
556
|
+
export declare function run_critique(config: ResolvedConfig, deps: CritiqueDeps): Promise<RunResult>;
|
|
470
557
|
//#endregion
|
|
471
558
|
//#region src/loop_state.d.ts
|
|
472
559
|
export declare function initial_state(): LoopState;
|
|
@@ -485,6 +572,13 @@ export declare function gate(config: ResolvedConfig, state: LoopState): {
|
|
|
485
572
|
export declare function status_of(value: LoopState, converged: boolean): RunStatus;
|
|
486
573
|
//#endregion
|
|
487
574
|
//#region src/sandbox.d.ts
|
|
575
|
+
/** Where volley inside the container keeps each run's state when no
|
|
576
|
+
* `--state-dir` names a root: the default root under the pinned `HOME`, since
|
|
577
|
+
* the image sets no `XDG_STATE_HOME`. `sandbox_run_args`'s `run_state` binds one
|
|
578
|
+
* run's host directory at that run's directory under it, so the state outlives
|
|
579
|
+
* the `--rm` container. Exported so a harness can map a contained run's
|
|
580
|
+
* `run_state_dir` back to the directory it mounted. */
|
|
581
|
+
export declare const SANDBOX_STATE_ROOT: string;
|
|
488
582
|
/** Canonical identity of the allowlist bridge the examples create (`docker
|
|
489
583
|
* network create --subnet <SANDBOX_NETWORK_SUBNET> <SANDBOX_NETWORK_NAME>`) and
|
|
490
584
|
* the host `DOCKER-USER` rules scope to. Exported so the blessed examples
|
|
@@ -530,7 +624,7 @@ export declare function network_run_args(network: SandboxNetwork, network_name:
|
|
|
530
624
|
/**
|
|
531
625
|
* The hardened `docker run` argv that launches volley inside its container.
|
|
532
626
|
* Pure and exported so the flag set is unit-testable without a
|
|
533
|
-
* daemon and the blessed examples
|
|
627
|
+
* daemon and the blessed examples and a parent harness can render it.
|
|
534
628
|
*
|
|
535
629
|
* A one-shot, foreground `--rm` container — the whole run *is* this
|
|
536
630
|
* container (an earlier design ran a detached `-d … sleep infinity` daemon volley
|
|
@@ -542,6 +636,25 @@ export declare function network_run_args(network: SandboxNetwork, network_name:
|
|
|
542
636
|
* the image entrypoint (the opt-in real-docker isolation test runs a raw `sh`
|
|
543
637
|
* this way).
|
|
544
638
|
*
|
|
639
|
+
* `run_state` binds the run's own state directory on the host,
|
|
640
|
+
* `<root>/<run_id>/`, at `<SANDBOX_STATE_ROOT>/<run_id>/`, where volley inside
|
|
641
|
+
* keeps that run's state when no `--state-dir` names a root, so it outlives the
|
|
642
|
+
* container at the path a run on the host would use. Only the one directory
|
|
643
|
+
* is mounted, never the root: the builder inside runs arbitrary commands, so
|
|
644
|
+
* it can rewrite its own run's state but can't read another run's trajectory
|
|
645
|
+
* or plant a check in another run's config. Its own run's state is still the
|
|
646
|
+
* builder's to rewrite, config.json included, so a host-side resume refuses
|
|
647
|
+
* that config, which volley signs only outside the sandbox (`./trust.ts`), and
|
|
648
|
+
* a directory holding a symlink: resume a contained run inside the container,
|
|
649
|
+
* with the same mount. Create the
|
|
650
|
+
* directory on the host first, empty (Docker on Linux makes a missing bind
|
|
651
|
+
* source owned by root, which the container's user can't write), and pass the
|
|
652
|
+
* same id to volley as `--run-id`. Keep it outside `build_root`, so the check
|
|
653
|
+
* never reads it, and pass no `XDG_STATE_HOME` in `env`, which would move the
|
|
654
|
+
* root off the mount. Omit it and no state is mounted: a contained run keeping
|
|
655
|
+
* its state under the default root then stops before spending (exit 5),
|
|
656
|
+
* since the read-only root filesystem leaves it nowhere to write.
|
|
657
|
+
*
|
|
545
658
|
* `store_volume` mounts the named volume as the pnpm store; omit it and no store
|
|
546
659
|
* is mounted. A store is shared by every run that mounts it, so a caller that
|
|
547
660
|
* installs dependencies in a separate container first can keep the builder's
|
|
@@ -551,13 +664,17 @@ export declare function network_run_args(network: SandboxNetwork, network_name:
|
|
|
551
664
|
* Never emits `--privileged` and refuses `--user 0`: volley must not hand
|
|
552
665
|
* the sandbox root or full capabilities. Everything writable under the
|
|
553
666
|
* `--read-only` rootfs is an explicit tmpfs or volume; the bind-mounted worktree
|
|
554
|
-
* is the
|
|
667
|
+
* and, when one is named, the state root are the host-visible write paths.
|
|
555
668
|
*/
|
|
556
669
|
export declare function sandbox_run_args(options: {
|
|
557
670
|
image: string;
|
|
558
671
|
build_root: string;
|
|
559
672
|
uid: number | null;
|
|
560
673
|
gid: number | null;
|
|
674
|
+
run_state?: {
|
|
675
|
+
root: string;
|
|
676
|
+
run_id: string;
|
|
677
|
+
} | null;
|
|
561
678
|
store_volume?: string | null;
|
|
562
679
|
network: SandboxNetwork;
|
|
563
680
|
network_name: string;
|
|
@@ -568,6 +685,13 @@ export declare function sandbox_run_args(options: {
|
|
|
568
685
|
command?: ReadonlyArray<string>;
|
|
569
686
|
}): string[];
|
|
570
687
|
//#endregion
|
|
688
|
+
//#region src/workspace.d.ts
|
|
689
|
+
/** The root each run's state sits under when `--state-dir` names none:
|
|
690
|
+
* `$XDG_STATE_HOME/volley`, else `<home>/.local/state/volley`. A relative
|
|
691
|
+
* `XDG_STATE_HOME` is ignored, as the XDG spec says. `home` is the host's by
|
|
692
|
+
* default; the sandbox passes its container's to spell the same root inside. */
|
|
693
|
+
export declare function default_state_root(env: Readonly<Record<string, string | undefined>>, home?: string): string;
|
|
694
|
+
//#endregion
|
|
571
695
|
//#region src/exit_codes.d.ts
|
|
572
696
|
export declare const EXIT_SUCCESS = 0;
|
|
573
697
|
export declare const EXIT_BUDGET_EXHAUSTED = 2;
|
|
@@ -580,5 +704,5 @@ export declare const EXIT_INTERRUPTED = 130;
|
|
|
580
704
|
export declare function exit_code_for_status(status: RunStatus): number;
|
|
581
705
|
export declare function exit_code_for_error(err: unknown): number;
|
|
582
706
|
//#endregion
|
|
583
|
-
export type { BuilderPermissionMode, CheckArtifact, CheckMode, CheckResult, CheckRunnerKind, CostSource, CriticPreset, CriticProvider, HaltReason, LoopState, PhaseRecord, RenderMode, Renderer, RendererOptions, ResolvedConfig, RunResult, RunStatus, SandboxNetwork, Verdict, VerdictOutput, VolleyConfig };
|
|
707
|
+
export type { BuilderPermissionMode, CheckArtifact, CheckMode, CheckResult, CheckRunnerKind, CostSource, CriticPreset, CriticProvider, CritiqueDeps, HaltReason, LoopState, PhaseRecord, RenderMode, Renderer, RendererOptions, ResolvedConfig, RunMode, RunResult, RunStatus, SandboxNetwork, Verdict, VerdictOutput, VolleyConfig };
|
|
584
708
|
//# sourceMappingURL=index.d.ts.map
|