@robmclarty/volley 0.7.0 → 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/CHANGELOG.md +20 -0
- package/README.md +277 -25
- package/dist/cli.js +3640 -2636
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +157 -30
- package/dist/index.js +589 -163
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/builder/presets/harness_append_acp.md +86 -0
- package/src/critic/presets/harness_append.md +2 -1
- package/src/critic/presets/harness_append_acp.md +27 -0
- package/src/critic/presets/harness_append_local.md +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { Engine, FinishReason, StreamChunk, Throughput, UsageTotals } from "fascicle";
|
|
1
|
+
import { Engine, FinishReason, StreamChunk, Throughput, Tool, ToolApprovalHandler, TrajectoryLogger, UsageTotals } from "fascicle";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
//#region src/types.d.ts
|
|
4
4
|
type Verdict = 'approved' | 'changes_requested';
|
|
5
5
|
type CriticPreset = 'reviewer' | 'optimizer' | 'researcher';
|
|
6
6
|
/** Transport for the critic role. The critic is read-only + schema-constrained,
|
|
7
|
-
* so it has always been able to run on a local model.
|
|
8
|
-
|
|
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';
|
|
9
11
|
/** Transport for the builder role. `claude_cli` is a full agentic Claude Code
|
|
10
|
-
* session;
|
|
11
|
-
* (write/exec tools supplied by the harness)
|
|
12
|
-
*
|
|
13
|
-
|
|
14
|
-
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';
|
|
15
16
|
type BuilderPermissionMode = 'acceptEdits' | 'bypassPermissions';
|
|
16
17
|
/** `auto`, `none`, or any shell command (the `string & {}` keeps the literal
|
|
17
18
|
* hints in editor completions without collapsing the union). */
|
|
@@ -22,6 +23,11 @@ type CostSource = 'provider_reported' | 'engine_derived' | 'unknown';
|
|
|
22
23
|
* died. Recorded alongside a critic retry so a degraded run says
|
|
23
24
|
* *what* failed, not just that it retried. */
|
|
24
25
|
type CauseKind = 'provider_5xx' | 'network' | 'unknown';
|
|
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';
|
|
25
31
|
type RunStatus = 'running' | 'success' | 'budget_exhausted' | 'cost_cap_reached' |
|
|
26
32
|
/** The builder edited the gate that judges it, under `--fail-on-gate-edit`:
|
|
27
33
|
* the run halts on the spot rather than iterating, because a check the builder
|
|
@@ -51,13 +57,14 @@ type VolleyConfig = {
|
|
|
51
57
|
gate_paths?: string[];
|
|
52
58
|
fail_on_gate_edit?: boolean;
|
|
53
59
|
sandbox_image?: string;
|
|
60
|
+
state_dir?: string;
|
|
54
61
|
verbose?: boolean;
|
|
55
62
|
quiet?: boolean;
|
|
56
63
|
json?: boolean;
|
|
57
64
|
show_thinking?: boolean;
|
|
58
65
|
dry_run?: boolean;
|
|
59
66
|
};
|
|
60
|
-
/** Fully resolved run configuration;
|
|
67
|
+
/** Fully resolved run configuration; the run's `config.json` mirrors this. */
|
|
61
68
|
type ResolvedConfig = {
|
|
62
69
|
version: 2;
|
|
63
70
|
run_id: string;
|
|
@@ -72,12 +79,13 @@ type ResolvedConfig = {
|
|
|
72
79
|
* It is normalized, and crossed to the host LLM endpoint when contained, only
|
|
73
80
|
* where it is used, because the crossing depends on where the run executes.
|
|
74
81
|
* Null falls back to the provider's env var (`VOLLEY_OLLAMA_URL` /
|
|
75
|
-
* `VOLLEY_LMSTUDIO_URL`), then its default, never to
|
|
76
|
-
* a provider volley reaches at a base URL (ollama,
|
|
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. */
|
|
77
85
|
builder_url: string | null;
|
|
78
86
|
builder_max_steps: number;
|
|
79
|
-
/** Operator opt-out permitting a
|
|
80
|
-
*
|
|
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. */
|
|
81
89
|
allow_unsandboxed_builder: boolean;
|
|
82
90
|
critic_model: string;
|
|
83
91
|
critic_provider: CriticProvider;
|
|
@@ -92,7 +100,7 @@ type ResolvedConfig = {
|
|
|
92
100
|
git_checkpoints: boolean;
|
|
93
101
|
/** Isolate the builder run's effects in a per-run git worktree: the
|
|
94
102
|
* builder/critic file tools' containment root moves to the worktree, while
|
|
95
|
-
*
|
|
103
|
+
* the run's state stays where `state_dir` puts it (the control plane). Off by
|
|
96
104
|
* default; requires the workspace to be a git repository. */
|
|
97
105
|
worktree: boolean;
|
|
98
106
|
/** Throw a `--worktree` run's effects away at teardown even when it converged:
|
|
@@ -119,6 +127,10 @@ type ResolvedConfig = {
|
|
|
119
127
|
* `VOLLEY_SANDBOX_IMAGE` override it. Meaningful only for a local builder —
|
|
120
128
|
* the `claude_cli` path never uses Docker. */
|
|
121
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;
|
|
122
134
|
workspace: string;
|
|
123
135
|
verbose: boolean;
|
|
124
136
|
quiet: boolean;
|
|
@@ -209,12 +221,15 @@ type PhaseRecord = {
|
|
|
209
221
|
retry_cause_kind?: CauseKind;
|
|
210
222
|
/** This local-critic verdict was rendered by the tool-less fallback rung:
|
|
211
223
|
* the tool-bearing critique kept dying on the provider's stream
|
|
212
|
-
* even after the bounded retry,
|
|
213
|
-
* grounded only by the criteria, the raw
|
|
214
|
-
* inventory. Set (true) on a degraded
|
|
215
|
-
* critique and on the builder. Additive, and a
|
|
216
|
-
* marked so it never passes silently as a full
|
|
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. */
|
|
217
230
|
critic_degraded?: boolean;
|
|
231
|
+
/** Why the verdict degraded, set with `critic_degraded`. Additive. */
|
|
232
|
+
degrade_cause?: DegradeCause;
|
|
218
233
|
/** Output tokens per second over the model's own turns (tool execution and
|
|
219
234
|
* retry backoff excluded), from fascicle's per-step timing. `basis` is
|
|
220
235
|
* `decode` when every turn streamed, `blended` when any round trip counted
|
|
@@ -287,12 +302,92 @@ type ResolveOptions = {
|
|
|
287
302
|
run_id?: string;
|
|
288
303
|
started_at?: string;
|
|
289
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';
|
|
290
310
|
};
|
|
291
311
|
/** Merge, expand, and validate a raw `VolleyConfig` into a `ResolvedConfig`.
|
|
292
312
|
* `check_resolved` comes from check detection against the workspace, so the
|
|
293
313
|
* SDK and the CLI resolve `auto` and a command check the same way. */
|
|
294
314
|
export declare function resolve_config(raw: VolleyConfig, options?: ResolveOptions): ResolvedConfig;
|
|
295
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
|
|
296
391
|
//#region src/render/format.d.ts
|
|
297
392
|
type Role = 'builder' | 'critic' | 'check';
|
|
298
393
|
//#endregion
|
|
@@ -303,6 +398,9 @@ type RendererOptions = {
|
|
|
303
398
|
show_thinking: boolean;
|
|
304
399
|
color: boolean;
|
|
305
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;
|
|
306
404
|
write?: (text: string) => void;
|
|
307
405
|
};
|
|
308
406
|
type Renderer = {
|
|
@@ -318,6 +416,30 @@ type Renderer = {
|
|
|
318
416
|
};
|
|
319
417
|
export declare function create_renderer(options: RendererOptions): Renderer;
|
|
320
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
|
|
321
443
|
//#region src/orchestrator.d.ts
|
|
322
444
|
type OrchestratorDeps = {
|
|
323
445
|
renderer: Renderer;
|
|
@@ -325,6 +447,22 @@ type OrchestratorDeps = {
|
|
|
325
447
|
* config. It serves both seats as-is, so the seat URLs then steer only
|
|
326
448
|
* volley's own requests (prewarm and the context probe), not model calls. */
|
|
327
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;
|
|
328
466
|
abort?: AbortSignal;
|
|
329
467
|
install_signal_handlers?: boolean;
|
|
330
468
|
};
|
|
@@ -346,17 +484,6 @@ export declare function gate(config: ResolvedConfig, state: LoopState): {
|
|
|
346
484
|
};
|
|
347
485
|
export declare function status_of(value: LoopState, converged: boolean): RunStatus;
|
|
348
486
|
//#endregion
|
|
349
|
-
//#region src/critic/run.d.ts
|
|
350
|
-
export declare const verdict_schema: z.ZodObject<{
|
|
351
|
-
verdict: z.ZodEnum<{
|
|
352
|
-
approved: "approved";
|
|
353
|
-
changes_requested: "changes_requested";
|
|
354
|
-
}>;
|
|
355
|
-
feedback: z.ZodString;
|
|
356
|
-
unmet_criteria: z.ZodArray<z.ZodString>;
|
|
357
|
-
}, z.core.$strip>;
|
|
358
|
-
type VerdictOutput = z.infer<typeof verdict_schema>;
|
|
359
|
-
//#endregion
|
|
360
487
|
//#region src/sandbox.d.ts
|
|
361
488
|
/** Canonical identity of the allowlist bridge the examples create (`docker
|
|
362
489
|
* network create --subnet <SANDBOX_NETWORK_SUBNET> <SANDBOX_NETWORK_NAME>`) and
|