@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/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' | 'interrupted' | 'error';
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
- * (`--state-dir`), resolved to an absolute path. Null keeps it at
132
- * `<workspace>/.volley/`, rotated aside by the next fresh run. */
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; `.volley/summary.json` mirrors this. */
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; `.volley/summary.json` mirrors it. */
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: `.volley/trajectory.jsonl`). */
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 / `--dry-run` preflight can render it.
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 one host-visible write path.
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