@robmclarty/volley 0.6.1 → 0.8.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
@@ -1,25 +1,18 @@
1
- import { UsageTotals, FinishReason, Throughput, StreamChunk, Engine } from 'fascicle';
2
- import { z } from 'zod';
3
-
4
- /**
5
- * Shared value and type contracts for volley.
6
- *
7
- * Type aliases are PascalCase; value-level identifiers are snake_case.
8
- * Fascicle's usage/cost types are used verbatim so summaries carry the
9
- * substrate's shapes, not a re-derivation.
10
- */
11
-
1
+ import { Engine, FinishReason, StreamChunk, Throughput, Tool, ToolApprovalHandler, TrajectoryLogger, UsageTotals } from "fascicle";
2
+ import { z } from "zod";
3
+ //#region src/types.d.ts
12
4
  type Verdict = 'approved' | 'changes_requested';
13
5
  type CriticPreset = 'reviewer' | 'optimizer' | 'researcher';
14
6
  /** Transport for the critic role. The critic is read-only + schema-constrained,
15
- * so it has always been able to run on a local model. */
16
- type CriticProvider = 'claude_cli' | 'ollama' | 'lmstudio';
7
+ * so it has always been able to run on a local model. `openrouter` reaches a
8
+ * hosted model over one API key and takes the local critic's tools and
9
+ * degradation ladder. */
10
+ type CriticProvider = 'claude_cli' | 'ollama' | 'lmstudio' | 'openrouter';
17
11
  /** Transport for the builder role. `claude_cli` is a full agentic Claude Code
18
- * session; the local providers run the builder as a volley-driven tool loop
19
- * (write/exec tools supplied by the harness). The execution path for the local
20
- * providers lands with the builder tool/termination work; this type and its
21
- * plumbing exist so that work has somewhere to attach (spec v3). */
22
- type BuilderProvider = 'claude_cli' | 'ollama' | 'lmstudio';
12
+ * session; every other provider runs the builder as a volley-driven tool loop
13
+ * (write/exec tools supplied by the harness), whether its model is local
14
+ * (`ollama`, `lmstudio`) or hosted (`openrouter`). */
15
+ type BuilderProvider = 'claude_cli' | 'ollama' | 'lmstudio' | 'openrouter';
23
16
  type BuilderPermissionMode = 'acceptEdits' | 'bypassPermissions';
24
17
  /** `auto`, `none`, or any shell command (the `string & {}` keeps the literal
25
18
  * hints in editor completions without collapsing the union). */
@@ -30,116 +23,139 @@ type CostSource = 'provider_reported' | 'engine_derived' | 'unknown';
30
23
  * died. Recorded alongside a critic retry so a degraded run says
31
24
  * *what* failed, not just that it retried. */
32
25
  type CauseKind = 'provider_5xx' | 'network' | 'unknown';
33
- type RunStatus = 'running' | 'success' | 'budget_exhausted' | 'cost_cap_reached'
26
+ /** Why a local critic's verdict came from the tool-less fallback:
27
+ * `stream_error`, the tool-bearing call kept dying on the provider's stream
28
+ * after the bounded retry; `step_exhaustion`, it spent its step budget (or
29
+ * stopped on a tool call) without producing a verdict. */
30
+ type DegradeCause = 'stream_error' | 'step_exhaustion';
31
+ type RunStatus = 'running' | 'success' | 'budget_exhausted' | 'cost_cap_reached' |
34
32
  /** The builder edited the gate that judges it, under `--fail-on-gate-edit`:
35
33
  * the run halts on the spot rather than iterating, because a check the builder
36
34
  * can rewrite is not evidence the work is done. */
37
- | 'gate_edit_blocked' | 'interrupted' | 'error';
35
+ 'gate_edit_blocked' | 'interrupted' | 'error';
38
36
  /** User-facing configuration, before resolution (config file / flags). */
39
37
  type VolleyConfig = {
40
- prompt: string;
41
- workspace: string;
42
- criteria: string;
43
- check?: CheckMode;
44
- builder_model?: string;
45
- builder_provider?: BuilderProvider;
46
- builder_max_steps?: number;
47
- allow_unsandboxed_builder?: boolean;
48
- critic_model?: string;
49
- critic_provider?: CriticProvider;
50
- builder_permission_mode?: BuilderPermissionMode;
51
- critic?: CriticPreset | (string & {});
52
- max_iterations?: number;
53
- max_cost_usd?: number | null;
54
- git_checkpoints?: boolean;
55
- worktree?: boolean;
56
- discard_worktree?: boolean;
57
- gate_paths?: string[];
58
- fail_on_gate_edit?: boolean;
59
- sandbox_image?: string;
60
- verbose?: boolean;
61
- quiet?: boolean;
62
- json?: boolean;
63
- show_thinking?: boolean;
64
- dry_run?: boolean;
38
+ prompt: string;
39
+ workspace: string;
40
+ criteria: string;
41
+ check?: CheckMode;
42
+ builder_model?: string;
43
+ builder_provider?: BuilderProvider;
44
+ builder_url?: string;
45
+ builder_max_steps?: number;
46
+ allow_unsandboxed_builder?: boolean;
47
+ critic_model?: string;
48
+ critic_provider?: CriticProvider;
49
+ critic_url?: string;
50
+ builder_permission_mode?: BuilderPermissionMode;
51
+ critic?: CriticPreset | (string & {});
52
+ max_iterations?: number;
53
+ max_cost_usd?: number | null;
54
+ git_checkpoints?: boolean;
55
+ worktree?: boolean;
56
+ discard_worktree?: boolean;
57
+ gate_paths?: string[];
58
+ fail_on_gate_edit?: boolean;
59
+ sandbox_image?: string;
60
+ state_dir?: string;
61
+ verbose?: boolean;
62
+ quiet?: boolean;
63
+ json?: boolean;
64
+ show_thinking?: boolean;
65
+ dry_run?: boolean;
65
66
  };
66
- /** Fully resolved run configuration; `.volley/config.json` mirrors this. */
67
+ /** Fully resolved run configuration; the run's `config.json` mirrors this. */
67
68
  type ResolvedConfig = {
68
- version: 2;
69
- run_id: string;
70
- started_at: string;
71
- prompt: string;
72
- criteria: string;
73
- check: CheckMode;
74
- check_resolved: CheckRunnerKind;
75
- builder_model: string;
76
- builder_provider: BuilderProvider;
77
- builder_max_steps: number;
78
- /** Operator opt-out permitting a local (non-`claude_cli`) builder to run
79
- * without a sandbox — it gets a real host bash. Refused by default. */
80
- allow_unsandboxed_builder: boolean;
81
- critic_model: string;
82
- critic_provider: CriticProvider;
83
- builder_permission_mode: BuilderPermissionMode;
84
- critic_preset: CriticPreset | 'custom';
85
- critic_prompt_path: string | null;
86
- max_iterations: number;
87
- max_cost_usd: number | null;
88
- git_checkpoints: boolean;
89
- /** Isolate the builder run's effects in a per-run git worktree: the
90
- * builder/critic file tools' containment root moves to the worktree, while
91
- * `.volley/` state stays under `workspace` (the control plane). Off by
92
- * default; requires the workspace to be a git repository. */
93
- worktree: boolean;
94
- /** Throw a `--worktree` run's effects away at teardown even when it converged:
95
- * the run branch is force-deleted rather than kept, because only the
96
- * verdicts are wanted. This is what `volley matrix` sweeps with — one config,
97
- * many builder×critic seats, no per-seat commits or branches left anywhere.
98
- * Refused without `--worktree`; `--git` still wins, so a `--worktree --git`
99
- * run integrates exactly as it always has. */
100
- discard_worktree: boolean;
101
- /** Glob patterns naming the *gate*: the tests, fixtures, and check
102
- * configuration that decide whether the builder's work passes. An edit to one
103
- * is reported to the critic and recorded in the summary — and refused outright
104
- * under `fail_on_gate_edit`. Defaults to `DEFAULT_GATE_PATTERNS`; a config's
105
- * own list replaces it, because what counts as the gate is the task's call
106
- * (a test-writing task edits tests by design). */
107
- gate_paths: string[];
108
- /** Halt the run when the builder edits a `gate_paths` match, instead of
109
- * reporting it and continuing. Off by default: a gate edit is legitimate often
110
- * enough that refusing it by default would break honest tasks, and the mark on
111
- * the summary already makes it impossible to miss. */
112
- fail_on_gate_edit: boolean;
113
- /** Container image the local-builder Docker sandbox runs. Defaults to
114
- * volley's own `Dockerfile`-built image; `--sandbox-image <tag>` /
115
- * `VOLLEY_SANDBOX_IMAGE` override it. Meaningful only for a local builder —
116
- * the `claude_cli` path never uses Docker. */
117
- sandbox_image: string;
118
- workspace: string;
119
- verbose: boolean;
120
- quiet: boolean;
121
- json: boolean;
122
- show_thinking: boolean;
69
+ version: 2;
70
+ run_id: string;
71
+ started_at: string;
72
+ prompt: string;
73
+ criteria: string;
74
+ check: CheckMode;
75
+ check_resolved: CheckRunnerKind;
76
+ builder_model: string;
77
+ builder_provider: BuilderProvider;
78
+ /** The builder seat's own model server base URL (`--builder-url`), as given.
79
+ * It is normalized, and crossed to the host LLM endpoint when contained, only
80
+ * where it is used, because the crossing depends on where the run executes.
81
+ * Null falls back to the provider's env var (`VOLLEY_OLLAMA_URL` /
82
+ * `VOLLEY_LMSTUDIO_URL` / `VOLLEY_OPENROUTER_URL`), then its default, never to
83
+ * the critic's URL. Only a provider volley reaches at a base URL (ollama,
84
+ * lmstudio, openrouter) takes one. */
85
+ builder_url: string | null;
86
+ builder_max_steps: number;
87
+ /** Operator opt-out permitting a non-`claude_cli` builder to run without a
88
+ * sandbox — it gets a real host bash. Refused by default. */
89
+ allow_unsandboxed_builder: boolean;
90
+ critic_model: string;
91
+ critic_provider: CriticProvider;
92
+ /** The critic seat's own model server base URL (`--critic-url`), as given;
93
+ * resolved like `builder_url`, and null never falls back to the builder's. */
94
+ critic_url: string | null;
95
+ builder_permission_mode: BuilderPermissionMode;
96
+ critic_preset: CriticPreset | 'custom';
97
+ critic_prompt_path: string | null;
98
+ max_iterations: number;
99
+ max_cost_usd: number | null;
100
+ git_checkpoints: boolean;
101
+ /** Isolate the builder run's effects in a per-run git worktree: the
102
+ * builder/critic file tools' containment root moves to the worktree, while
103
+ * the run's state stays where `state_dir` puts it (the control plane). Off by
104
+ * default; requires the workspace to be a git repository. */
105
+ worktree: boolean;
106
+ /** Throw a `--worktree` run's effects away at teardown even when it converged:
107
+ * the run branch is force-deleted rather than kept, because only the
108
+ * verdicts are wanted. This is what `volley matrix` sweeps with — one config,
109
+ * many builder×critic seats, no per-seat commits or branches left anywhere.
110
+ * Refused without `--worktree`; `--git` still wins, so a `--worktree --git`
111
+ * run integrates exactly as it always has. */
112
+ discard_worktree: boolean;
113
+ /** Glob patterns naming the *gate*: the tests, fixtures, and check
114
+ * configuration that decide whether the builder's work passes. An edit to one
115
+ * is reported to the critic and recorded in the summary — and refused outright
116
+ * under `fail_on_gate_edit`. Defaults to `DEFAULT_GATE_PATTERNS`; a config's
117
+ * own list replaces it, because what counts as the gate is the task's call
118
+ * (a test-writing task edits tests by design). */
119
+ gate_paths: string[];
120
+ /** Halt the run when the builder edits a `gate_paths` match, instead of
121
+ * reporting it and continuing. Off by default: a gate edit is legitimate often
122
+ * enough that refusing it by default would break honest tasks, and the mark on
123
+ * the summary already makes it impossible to miss. */
124
+ fail_on_gate_edit: boolean;
125
+ /** Container image the local-builder Docker sandbox runs. Defaults to
126
+ * volley's own `Dockerfile`-built image; `--sandbox-image <tag>` /
127
+ * `VOLLEY_SANDBOX_IMAGE` override it. Meaningful only for a local builder —
128
+ * the `claude_cli` path never uses Docker. */
129
+ 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. */
133
+ state_dir: string | null;
134
+ workspace: string;
135
+ verbose: boolean;
136
+ quiet: boolean;
137
+ json: boolean;
138
+ show_thinking: boolean;
123
139
  };
124
140
  /** One failing check slot's raw artifact, for the critic prompt. */
125
141
  type CheckArtifact = {
126
- slot: string;
127
- path: string | null;
128
- content: string;
129
- truncated: boolean;
142
+ slot: string;
143
+ path: string | null;
144
+ content: string;
145
+ truncated: boolean;
130
146
  };
131
147
  type CheckResult = {
132
- ran: boolean;
133
- runner: CheckRunnerKind;
134
- ok: boolean;
135
- exit_code: number | null;
136
- duration_ms: number;
137
- failing_slots: string[];
138
- detail: CheckArtifact[];
139
- /** Raw checkride summary JSON (when the runner is checkride). */
140
- summary?: unknown;
141
- /** Captured combined output (when the runner is a shell command). */
142
- log?: string;
148
+ ran: boolean;
149
+ runner: CheckRunnerKind;
150
+ ok: boolean;
151
+ exit_code: number | null;
152
+ duration_ms: number;
153
+ failing_slots: string[];
154
+ detail: CheckArtifact[];
155
+ /** Raw checkride summary JSON (when the runner is checkride). */
156
+ summary?: unknown;
157
+ /** Captured combined output (when the runner is a shell command). */
158
+ log?: string;
143
159
  };
144
160
  /** How one path changed between the run's baseline and the builder's output.
145
161
  * `renamed` covers git's rename/copy detection (the destination path is the one
@@ -150,175 +166,310 @@ type ChangeStatus = 'added' | 'modified' | 'deleted' | 'renamed' | 'unknown';
150
166
  * passes. A gate edit is not misconduct by itself (some tasks are *about* the
151
167
  * tests); it is the fact that makes a green check prove less. */
152
168
  type ChangedFile = {
153
- path: string;
154
- status: ChangeStatus;
155
- gate: boolean;
169
+ path: string;
170
+ status: ChangeStatus;
171
+ gate: boolean;
156
172
  };
157
173
  /** What the builder changed since the run's baseline, as the harness sees it
158
174
  * (`src/changes.ts`). Null in `LoopState` when the build root is not a git
159
175
  * repository — change detection is evidence, never a precondition. */
160
176
  type ChangeSet = {
161
- /** The commit the changes are measured against — the build root's HEAD before
162
- * the first iteration. Null in a repository with no commits yet, where only
163
- * untracked files can be reported. */
164
- baseline: string | null;
165
- /** The changed paths, sorted, capped at the collection limit. */
166
- files: ChangedFile[];
167
- /** Every gate-matching path — computed over the *full* list before the cap,
168
- * so truncation can never hide a gate edit. */
169
- gate_edits: string[];
170
- /** How many paths changed in total, before the cap. */
171
- total: number;
172
- truncated: boolean;
177
+ /** The commit the changes are measured against — the build root's HEAD before
178
+ * the first iteration. Null in a repository with no commits yet, where only
179
+ * untracked files can be reported. */
180
+ baseline: string | null;
181
+ /** The changed paths, sorted, capped at the collection limit. */
182
+ files: ChangedFile[];
183
+ /** Every gate-matching path — computed over the *full* list before the cap,
184
+ * so truncation can never hide a gate edit. */
185
+ gate_edits: string[];
186
+ /** How many paths changed in total, before the cap. */
187
+ total: number;
188
+ truncated: boolean;
173
189
  };
174
190
  /** Per-phase record for iteration summaries. */
175
191
  type PhaseRecord = {
176
- provider: string;
177
- model: string;
178
- session_id: string | null;
179
- /** Wall-clock of the phase's model work, the same measure for every provider
180
- * so an all-local run and an all-Claude run compare directly. The builder's is
181
- * fascicle's `GenerateResult.timing`: the whole call, every turn and the tools
182
- * between them. The critic's spans its whole degradation ladder, failed
183
- * attempts included, since no one result's timing covers the attempts before
184
- * it. Neither counts the Ollama prewarm. */
185
- duration_ms: number;
186
- usage: UsageTotals;
187
- cost_usd: number | null;
188
- cost_source: CostSource;
189
- /** How the phase's generate call ended. For a local builder,
190
- * `'max_steps'` is a budget cutoff surfaced as a warning, not an error. */
191
- finish_reason: FinishReason;
192
- /** Tool calls fascicle executed in this phase, and how many were recovered
193
- * from assistant text rather than returned structurally — the raw
194
- * counts behind the salvage-rate health metric. */
195
- tool_calls: number;
196
- salvaged_tool_calls: number;
197
- /** Provider-error retries that preceded this phase's successful call
198
- * — the local-critic degradation ladder's first rung: a stochastic
199
- * Ollama stream death is retried once before falling back. Set on the critic
200
- * record (0 when the first call succeeded); absent on the builder, which is
201
- * not retried this build. Additive — old summary consumers ignore it. */
202
- retries?: number;
203
- /** The `cause_kind` of the retried provider error, recorded with `retries`.
204
- * Absent when no retry happened. */
205
- retry_cause_kind?: CauseKind;
206
- /** This local-critic verdict was rendered by the tool-less fallback rung:
207
- * the tool-bearing critique kept dying on the provider's stream
208
- * even after the bounded retry, so the critic judged with no read access —
209
- * grounded only by the criteria, the raw check artifacts, and a workspace file
210
- * inventory. Set (true) on a degraded critic record; absent on a full
211
- * critique and on the builder. Additive, and a degraded verdict is *always*
212
- * marked so it never passes silently as a full one. */
213
- critic_degraded?: boolean;
214
- /** Output tokens per second over the model's own turns (tool execution and
215
- * retry backoff excluded), from fascicle's per-step timing. `basis` is
216
- * `decode` when every turn streamed, `blended` when any round trip counted
217
- * prefill and network too — always the case for `claude_cli`, whose rate
218
- * fascicle derives from the CLI's reported API time. Absent when no turn
219
- * carried timing. Additive. */
220
- throughput?: Throughput;
192
+ provider: string;
193
+ model: string;
194
+ session_id: string | null;
195
+ /** Wall-clock of the phase's model work, the same measure for every provider
196
+ * so an all-local run and an all-Claude run compare directly. The builder's is
197
+ * fascicle's `GenerateResult.timing`: the whole call, every turn and the tools
198
+ * between them. The critic's spans its whole degradation ladder, failed
199
+ * attempts included, since no one result's timing covers the attempts before
200
+ * it. Neither counts the Ollama prewarm. */
201
+ duration_ms: number;
202
+ usage: UsageTotals;
203
+ cost_usd: number | null;
204
+ cost_source: CostSource;
205
+ /** How the phase's generate call ended. For a local builder,
206
+ * `'max_steps'` is a budget cutoff surfaced as a warning, not an error. */
207
+ finish_reason: FinishReason;
208
+ /** Tool calls fascicle executed in this phase, and how many were recovered
209
+ * from assistant text rather than returned structurally — the raw
210
+ * counts behind the salvage-rate health metric. */
211
+ tool_calls: number;
212
+ salvaged_tool_calls: number;
213
+ /** Provider-error retries that preceded this phase's successful call
214
+ * — the local-critic degradation ladder's first rung: a stochastic
215
+ * Ollama stream death is retried once before falling back. Set on the critic
216
+ * record (0 when the first call succeeded); absent on the builder, which is
217
+ * not retried this build. Additive — old summary consumers ignore it. */
218
+ retries?: number;
219
+ /** The `cause_kind` of the retried provider error, recorded with `retries`.
220
+ * Absent when no retry happened. */
221
+ retry_cause_kind?: CauseKind;
222
+ /** This local-critic verdict was rendered by the tool-less fallback rung:
223
+ * the tool-bearing critique kept dying on the provider's stream
224
+ * even after the bounded retry, or ran out of steps before its verdict, so the
225
+ * critic judged with no read access — grounded only by the criteria, the raw
226
+ * check artifacts, and a workspace file inventory. Set (true) on a degraded
227
+ * critic record; absent on a full critique and on the builder. Additive, and a
228
+ * degraded verdict is *always* marked so it never passes silently as a full
229
+ * one. */
230
+ critic_degraded?: boolean;
231
+ /** Why the verdict degraded, set with `critic_degraded`. Additive. */
232
+ degrade_cause?: DegradeCause;
233
+ /** Output tokens per second over the model's own turns (tool execution and
234
+ * retry backoff excluded), from fascicle's per-step timing. `basis` is
235
+ * `decode` when every turn streamed, `blended` when any round trip counted
236
+ * prefill and network too — always the case for `claude_cli`, whose rate
237
+ * fascicle derives from the CLI's reported API time. Absent when no turn
238
+ * carried timing. Additive.
239
+ *
240
+ * On Ollama, `decode` holds only while a turn's first chunk arrives as its
241
+ * first token is generated. Ollama sends a tool call whole, so a turn whose
242
+ * only output is a tool call dispatches nothing until generation ends, and
243
+ * its tokens are divided by the last few milliseconds: a non-thinking seat's
244
+ * rate reads far too high until robmclarty/fascicle#12 times those turns
245
+ * another way. A thinking seat needs ai-sdk-ollama 4.3.0 or later, which
246
+ * streams the reasoning even though volley never sets `think`. */
247
+ throughput?: Throughput;
221
248
  };
222
249
  type HaltReason = 'cost_cap' | 'gate_edit' | null;
223
250
  /** Carry-state threaded through the fascicle loop. */
224
251
  type LoopState = {
225
- iteration: number;
226
- iteration_started_at: string;
227
- feedback: string | null;
228
- verdict: Verdict | null;
229
- unmet_criteria: string[];
230
- check: CheckResult | null;
231
- /** What the builder changed this iteration, measured against the run's
232
- * baseline (`src/changes.ts`). Null when the build root is not a git
233
- * repository, and before the first `build` step of a run. */
234
- changes: ChangeSet | null;
235
- builder: PhaseRecord | null;
236
- critic: PhaseRecord | null;
237
- total_usage: UsageTotals;
238
- total_cost_usd: number;
239
- builder_cost_usd: number;
240
- critic_cost_usd: number;
241
- check_duration_ms: number;
242
- iteration_cost_usd: number;
243
- halt: HaltReason;
244
- cost_warned: boolean;
252
+ iteration: number;
253
+ iteration_started_at: string;
254
+ feedback: string | null;
255
+ verdict: Verdict | null;
256
+ unmet_criteria: string[];
257
+ check: CheckResult | null;
258
+ /** What the builder changed this iteration, measured against the run's
259
+ * baseline (`src/changes.ts`). Null when the build root is not a git
260
+ * repository, and before the first `build` step of a run. */
261
+ changes: ChangeSet | null;
262
+ builder: PhaseRecord | null;
263
+ critic: PhaseRecord | null;
264
+ total_usage: UsageTotals;
265
+ total_cost_usd: number;
266
+ builder_cost_usd: number;
267
+ critic_cost_usd: number;
268
+ check_duration_ms: number;
269
+ iteration_cost_usd: number;
270
+ halt: HaltReason;
271
+ cost_warned: boolean;
245
272
  };
246
273
  /** Projection of final loop state; `.volley/summary.json` mirrors this. */
247
274
  type RunResult = {
248
- run_id: string;
249
- status: RunStatus;
250
- started_at: string;
251
- completed_at: string | null;
252
- iterations_completed: number;
253
- total_usage: UsageTotals;
254
- total_cost_usd: number;
255
- builder_cost_usd: number;
256
- critic_cost_usd: number;
257
- check_duration_ms: number;
258
- final_verdict: Verdict | null;
259
- /** The run branch teardown left behind because this run's work was never
260
- * integrated: under `--worktree` without `--git` a converged run has no
261
- * other copy of its effects, so teardown commits them onto `volley/<run id>`
262
- * and keeps the branch instead of force-deleting it. Null whenever nothing
263
- * survived — every non-worktree run, every integrated run, every discarded
264
- * one. `--json` consumers read it here; `.volley/summary.json` mirrors it. */
265
- salvaged_branch: string | null;
275
+ run_id: string;
276
+ status: RunStatus;
277
+ started_at: string;
278
+ completed_at: string | null;
279
+ iterations_completed: number;
280
+ total_usage: UsageTotals;
281
+ total_cost_usd: number;
282
+ builder_cost_usd: number;
283
+ critic_cost_usd: number;
284
+ check_duration_ms: number;
285
+ final_verdict: Verdict | null;
286
+ /** The run branch teardown left behind because this run's work was never
287
+ * integrated: under `--worktree` without `--git` a converged run has no
288
+ * other copy of its effects, so teardown commits them onto `volley/<run id>`
289
+ * and keeps the branch instead of force-deleting it. Null whenever nothing
290
+ * survived — every non-worktree run, every integrated run, every discarded
291
+ * one. `--json` consumers read it here; `.volley/summary.json` mirrors it. */
292
+ salvaged_branch: string | null;
266
293
  };
267
-
294
+ //#endregion
295
+ //#region src/config.d.ts
268
296
  /** `@path/to/file` reads the file; any other string passes through. */
269
- declare function expand_at_file(value: string, cwd: string): string;
297
+ export declare function expand_at_file(value: string, cwd: string): string;
270
298
  /** Load a `volley.config.ts` / `.js` file's default export. */
271
- declare function load_config_file(path: string): Promise<VolleyConfig>;
299
+ export declare function load_config_file(path: string): Promise<VolleyConfig>;
272
300
  type ResolveOptions = {
273
- cwd?: string;
274
- run_id?: string;
275
- started_at?: string;
276
- env?: Record<string, string | undefined>;
301
+ cwd?: string;
302
+ run_id?: string;
303
+ started_at?: string;
304
+ env?: Record<string, string | undefined>;
305
+ /** What happens to a tool-loop builder with nothing between its commands
306
+ * and the host (`unguarded_builder`): the CLI `refuse`s it, the default,
307
+ * while under `volley acp` each of its commands `ask`s a person first, which
308
+ * takes the container's place. */
309
+ uncontained_builder?: 'refuse' | 'ask';
277
310
  };
278
311
  /** Merge, expand, and validate a raw `VolleyConfig` into a `ResolvedConfig`.
279
312
  * `check_resolved` comes from check detection against the workspace, so the
280
313
  * SDK and the CLI resolve `auto` and a command check the same way. */
281
- declare function resolve_config(raw: VolleyConfig, options?: ResolveOptions): ResolvedConfig;
282
-
314
+ export declare function resolve_config(raw: VolleyConfig, options?: ResolveOptions): ResolvedConfig;
315
+ //#endregion
316
+ //#region src/builder/tools.d.ts
317
+ /** The raw outcome of running one bash command, before the model-facing
318
+ * truncation + timeout marker the `bash` tool applies. `status` is the exit code
319
+ * (null when the process was killed by a signal — e.g. the timeout SIGKILL);
320
+ * `timed_out` marks the timeout path. */
321
+ type BashOutcome = {
322
+ status: number | null;
323
+ stdout: string;
324
+ stderr: string;
325
+ timed_out: boolean;
326
+ };
327
+ /** How the `bash` tool runs a command: the local `host_bash_executor`
328
+ * (`spawnSync`) — which under whole-process containment runs *inside*
329
+ * volley's own hardened container, no `docker exec` hop. The executor runs one
330
+ * command to completion and returns its raw outcome; the `bash` tool owns the
331
+ * truncation + timeout marker. Kept as an injectable seam so tests can supply a
332
+ * fake and a future shape could re-point it (the `docker exec` executor was
333
+ * retired). */
334
+ type BashExecutor = (command: string, options: {
335
+ timeout_ms: number;
336
+ max_capture_bytes: number;
337
+ }) => BashOutcome;
338
+ //#endregion
339
+ //#region src/workspace_tools.d.ts
340
+ /** Which tool set a seat holds, so its prompt names the tools it has: volley's
341
+ * own (`'volley'`), or fascicle's workspace tools under `volley acp`
342
+ * (`'acp'`). */
343
+ type ToolSet = 'volley' | 'acp';
344
+ //#endregion
345
+ //#region src/builder.d.ts
346
+ type BuilderDeps = {
347
+ engine: Engine;
348
+ config: ResolvedConfig;
349
+ on_chunk: (chunk: StreamChunk) => void;
350
+ /** The `bash` executor for a sandboxed local builder (`docker exec` against
351
+ * the run's container), or null to use the host `spawnSync` default (the
352
+ * unsandboxed escape hatch, or `claude_cli` which supplies no volley tools). */
353
+ bash_executor?: BashExecutor | null;
354
+ /** Tools to hand the builder in place of volley's own set; absent, it gets
355
+ * that set. A `claude_cli` builder brings its own tools and never takes
356
+ * these. */
357
+ tools?: ReadonlyArray<Tool>;
358
+ /** Which set `tools` is, so the prompt names the tools the builder holds;
359
+ * absent, volley's own. */
360
+ tool_set?: ToolSet;
361
+ /** Answers each call to a tool that asks first (`needs_approval`). Like
362
+ * `tools`, it reaches every builder but `claude_cli`. Absent, such a call
363
+ * fails closed. */
364
+ on_tool_approval?: ToolApprovalHandler;
365
+ };
366
+ //#endregion
367
+ //#region src/critic/run.d.ts
368
+ export declare const verdict_schema: z.ZodObject<{
369
+ verdict: z.ZodEnum<{
370
+ approved: "approved";
371
+ changes_requested: "changes_requested";
372
+ }>;
373
+ feedback: z.ZodString;
374
+ unmet_criteria: z.ZodArray<z.ZodString>;
375
+ }, z.core.$strip>;
376
+ type VerdictOutput = z.infer<typeof verdict_schema>;
377
+ type CriticDeps = {
378
+ engine: Engine;
379
+ config: ResolvedConfig;
380
+ on_chunk: (chunk: StreamChunk) => void;
381
+ /** Tools to hand the critic's tool-bearing call in place of volley's reads;
382
+ * absent, it gets those. A `claude_cli` critic reads through the CLI's own
383
+ * tools and never takes these. The critic takes no approval handler, since
384
+ * nothing it reads with should ever ask. */
385
+ tools?: ReadonlyArray<Tool>;
386
+ /** Which set `tools` is, so the harness append names the reads the critic
387
+ * holds; absent, volley's own. */
388
+ tool_set?: ToolSet;
389
+ };
390
+ //#endregion
391
+ //#region src/render/format.d.ts
283
392
  type Role = 'builder' | 'critic' | 'check';
284
-
285
- /**
286
- * Live output renderer: consumes `StreamChunk`s from each
287
- * `generate` call plus volley's own phase/cost lines, and writes human
288
- * progress to stderr. Streaming is a display concern only — the trajectory
289
- * jsonl is the durable record.
290
- */
291
-
393
+ //#endregion
394
+ //#region src/render/renderer.d.ts
292
395
  type RenderMode = 'default' | 'verbose' | 'quiet' | 'json';
293
396
  type RendererOptions = {
294
- mode: RenderMode;
295
- show_thinking: boolean;
296
- color: boolean;
297
- max_cost_usd: number | null;
298
- write?: (text: string) => void;
397
+ mode: RenderMode;
398
+ show_thinking: boolean;
399
+ color: boolean;
400
+ max_cost_usd: number | null;
401
+ /** Where the run's trajectory is written, named when verbose output is cut
402
+ * short (default: `.volley/trajectory.jsonl`). */
403
+ trajectory_path?: string;
404
+ write?: (text: string) => void;
299
405
  };
300
406
  type Renderer = {
301
- builder_chunk: (chunk: StreamChunk) => void;
302
- critic_chunk: (chunk: StreamChunk) => void;
303
- phase_start: (iteration: number, phase: Role) => void;
304
- phase_end: (iteration: number, phase: Role, ok: boolean, detail?: string) => void;
305
- cost_line: (iteration: number, phase: Role, phase_cost: number | null, run_total: number) => void;
306
- info: (message: string) => void;
307
- warn: (message: string) => void;
308
- error: (message: string) => void;
309
- final_summary: (result: RunResult) => void;
407
+ builder_chunk: (chunk: StreamChunk) => void;
408
+ critic_chunk: (chunk: StreamChunk) => void;
409
+ phase_start: (iteration: number, phase: Role) => void;
410
+ phase_end: (iteration: number, phase: Role, ok: boolean, detail?: string) => void;
411
+ cost_line: (iteration: number, phase: Role, phase_cost: number | null, run_total: number) => void;
412
+ info: (message: string) => void;
413
+ warn: (message: string) => void;
414
+ error: (message: string) => void;
415
+ final_summary: (result: RunResult) => void;
310
416
  };
311
- declare function create_renderer(options: RendererOptions): Renderer;
312
-
417
+ export declare function create_renderer(options: RendererOptions): Renderer;
418
+ //#endregion
419
+ //#region src/phases.d.ts
420
+ /** How the check runs when the caller sets it, as `volley acp` does. Left
421
+ * out, the check inherits volley's environment and runs unasked, as on the
422
+ * CLI. */
423
+ type CheckGate = {
424
+ /** The whole environment the check runs with, in place of volley's own. */
425
+ env?: Readonly<Record<string, string>>;
426
+ /** Asked on the check's card before each check, the way a model call asks
427
+ * before a gated tool runs. Anything short of an allow keeps the check from
428
+ * running, and it's recorded as failed. */
429
+ on_approval?: ToolApprovalHandler;
430
+ };
431
+ //#endregion
432
+ //#region src/flow.d.ts
433
+ /** The builder seat: the engine its calls go through, and, when the caller
434
+ * injects them, the tools that replace volley's own set, the name of that set
435
+ * (which its prompt names), and the handler that answers their approval
436
+ * requests. */
437
+ type BuilderSeat = Pick<BuilderDeps, 'engine' | 'tools' | 'tool_set' | 'on_tool_approval'>;
438
+ /** The critic seat: its engine and, when injected, the tools that replace its
439
+ * reads and the name of their set. It takes no approval handler, since its
440
+ * reads never ask. */
441
+ type CriticSeat = Pick<CriticDeps, 'engine' | 'tools' | 'tool_set'>;
442
+ //#endregion
443
+ //#region src/orchestrator.d.ts
313
444
  type OrchestratorDeps = {
314
- renderer: Renderer;
315
- engine?: Engine;
316
- abort?: AbortSignal;
317
- install_signal_handlers?: boolean;
445
+ renderer: Renderer;
446
+ /** An engine to run both seats on in place of the ones built from the
447
+ * config. It serves both seats as-is, so the seat URLs then steer only
448
+ * volley's own requests (prewarm and the context probe), not model calls. */
449
+ engine?: Engine;
450
+ /** What to hand each seat in place of volley's own wiring: the builder's
451
+ * tools and the handler that answers their approval requests, and the
452
+ * critic's tools. A seat or a field left out keeps today's wiring, and a
453
+ * `claude_cli` seat, which brings its own tools, takes none of it. */
454
+ seats?: {
455
+ builder?: Omit<BuilderSeat, 'engine'>;
456
+ critic?: Omit<CriticSeat, 'engine'>;
457
+ };
458
+ /** A second sink for the run's trajectory, beside the run's
459
+ * `trajectory.jsonl`. With one, the flow runs through `run.stream`, so the
460
+ * sink also receives each model call's streamed chunks as `model_chunk`
461
+ * events, which the file never keeps. */
462
+ trajectory?: TrajectoryLogger;
463
+ /** The environment the check runs with and the approval it asks first, in
464
+ * place of inheriting volley's environment and running unasked. */
465
+ check?: CheckGate;
466
+ abort?: AbortSignal;
467
+ install_signal_handlers?: boolean;
318
468
  };
319
- declare function run_volley(config: ResolvedConfig, deps: OrchestratorDeps, resume_from?: LoopState | null): Promise<RunResult>;
320
-
321
- declare function initial_state(): LoopState;
469
+ export declare function run_volley(config: ResolvedConfig, deps: OrchestratorDeps, resume_from?: LoopState | null): Promise<RunResult>;
470
+ //#endregion
471
+ //#region src/loop_state.d.ts
472
+ export declare function initial_state(): LoopState;
322
473
  /** All stopping conditions live here: acceptance, cost cap, the gate
323
474
  * edit refusal, and — implicitly via `max_rounds` — the iteration budget.
324
475
  * Success wins when it and the cap land on the same iteration.
@@ -327,34 +478,19 @@ declare function initial_state(): LoopState;
327
478
  * it: the run passed a check the builder had rewritten, so the pass is exactly
328
479
  * what is in question. It stops the run instead of iterating, because a model
329
480
  * that just edited the gate is not a promising candidate to be asked again. */
330
- declare function gate(config: ResolvedConfig, state: LoopState): {
331
- stop: boolean;
332
- state: LoopState;
481
+ export declare function gate(config: ResolvedConfig, state: LoopState): {
482
+ stop: boolean;
483
+ state: LoopState;
333
484
  };
334
- declare function status_of(value: LoopState, converged: boolean): RunStatus;
335
-
336
- /**
337
- * Critic invocation: a read-only session (`claude_cli`, or a local model with
338
- * volley's read tools) returning a schema-validated structured verdict. The
339
- * harness — not the critic — writes `.volley/feedback.md` and `.volley/verdict`.
340
- */
341
-
342
- declare const verdict_schema: z.ZodObject<{
343
- verdict: z.ZodEnum<{
344
- approved: "approved";
345
- changes_requested: "changes_requested";
346
- }>;
347
- feedback: z.ZodString;
348
- unmet_criteria: z.ZodArray<z.ZodString>;
349
- }, z.core.$strip>;
350
- type VerdictOutput = z.infer<typeof verdict_schema>;
351
-
485
+ export declare function status_of(value: LoopState, converged: boolean): RunStatus;
486
+ //#endregion
487
+ //#region src/sandbox.d.ts
352
488
  /** Canonical identity of the allowlist bridge the examples create (`docker
353
489
  * network create --subnet <SANDBOX_NETWORK_SUBNET> <SANDBOX_NETWORK_NAME>`) and
354
490
  * the host `DOCKER-USER` rules scope to. Exported so the blessed examples
355
491
  * share one spelling of the invocation spec. */
356
- declare const SANDBOX_NETWORK_NAME = "volley-sandbox-net";
357
- declare const SANDBOX_NETWORK_SUBNET = "172.31.99.0/24";
492
+ export declare const SANDBOX_NETWORK_NAME = "volley-sandbox-net";
493
+ export declare const SANDBOX_NETWORK_SUBNET = "172.31.99.0/24";
358
494
  /**
359
495
  * The container's egress posture. Under whole-process containment
360
496
  * the model client and the `fetch` tool run inside the container, so its
@@ -390,7 +526,7 @@ type SandboxNetwork = 'none' | 'allowlist';
390
526
  * collapses the allowlist targets onto the host gateway (`host.docker.internal`,
391
527
  * the Linux-portable spelling — harmless where Docker Desktop already provides it).
392
528
  */
393
- declare function network_run_args(network: SandboxNetwork, network_name: string): string[];
529
+ export declare function network_run_args(network: SandboxNetwork, network_name: string): string[];
394
530
  /**
395
531
  * The hardened `docker run` argv that launches volley inside its container.
396
532
  * Pure and exported so the flag set is unit-testable without a
@@ -417,30 +553,32 @@ declare function network_run_args(network: SandboxNetwork, network_name: string)
417
553
  * `--read-only` rootfs is an explicit tmpfs or volume; the bind-mounted worktree
418
554
  * is the one host-visible write path.
419
555
  */
420
- declare function sandbox_run_args(options: {
421
- image: string;
422
- build_root: string;
423
- uid: number | null;
424
- gid: number | null;
425
- store_volume?: string | null;
426
- network: SandboxNetwork;
427
- network_name: string;
428
- name?: string | null;
429
- env?: ReadonlyArray<readonly [string, string]>;
430
- labels?: ReadonlyArray<readonly [string, string]>;
431
- entrypoint?: string | null;
432
- command?: ReadonlyArray<string>;
556
+ export declare function sandbox_run_args(options: {
557
+ image: string;
558
+ build_root: string;
559
+ uid: number | null;
560
+ gid: number | null;
561
+ store_volume?: string | null;
562
+ network: SandboxNetwork;
563
+ network_name: string;
564
+ name?: string | null;
565
+ env?: ReadonlyArray<readonly [string, string]>;
566
+ labels?: ReadonlyArray<readonly [string, string]>;
567
+ entrypoint?: string | null;
568
+ command?: ReadonlyArray<string>;
433
569
  }): string[];
434
-
435
- declare const EXIT_SUCCESS = 0;
436
- declare const EXIT_BUDGET_EXHAUSTED = 2;
437
- declare const EXIT_BUILDER_ERROR = 3;
438
- declare const EXIT_CHECK_ERROR = 4;
439
- declare const EXIT_CONFIG_ERROR = 5;
440
- declare const EXIT_CRITIC_ERROR = 6;
441
- declare const EXIT_COST_CAP = 7;
442
- declare const EXIT_INTERRUPTED = 130;
443
- declare function exit_code_for_status(status: RunStatus): number;
444
- declare function exit_code_for_error(err: unknown): number;
445
-
446
- export { type BuilderPermissionMode, type CheckArtifact, type CheckMode, type CheckResult, type CheckRunnerKind, type CostSource, type CriticPreset, type CriticProvider, EXIT_BUDGET_EXHAUSTED, EXIT_BUILDER_ERROR, EXIT_CHECK_ERROR, EXIT_CONFIG_ERROR, EXIT_COST_CAP, EXIT_CRITIC_ERROR, EXIT_INTERRUPTED, EXIT_SUCCESS, type HaltReason, type LoopState, type PhaseRecord, type RenderMode, type Renderer, type RendererOptions, type ResolvedConfig, type RunResult, type RunStatus, SANDBOX_NETWORK_NAME, SANDBOX_NETWORK_SUBNET, type SandboxNetwork, type Verdict, type VerdictOutput, type VolleyConfig, create_renderer, exit_code_for_error, exit_code_for_status, expand_at_file, gate, initial_state, load_config_file, network_run_args, resolve_config, run_volley, sandbox_run_args, status_of, verdict_schema };
570
+ //#endregion
571
+ //#region src/exit_codes.d.ts
572
+ export declare const EXIT_SUCCESS = 0;
573
+ export declare const EXIT_BUDGET_EXHAUSTED = 2;
574
+ export declare const EXIT_BUILDER_ERROR = 3;
575
+ export declare const EXIT_CHECK_ERROR = 4;
576
+ export declare const EXIT_CONFIG_ERROR = 5;
577
+ export declare const EXIT_CRITIC_ERROR = 6;
578
+ export declare const EXIT_COST_CAP = 7;
579
+ export declare const EXIT_INTERRUPTED = 130;
580
+ export declare function exit_code_for_status(status: RunStatus): number;
581
+ export declare function exit_code_for_error(err: unknown): number;
582
+ //#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 };
584
+ //# sourceMappingURL=index.d.ts.map