cargo-hauler 0.4.1 → 0.4.3
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 +119 -39
- package/artifact/agent-bundle.manifest.json +1 -1
- package/artifact/claude/.claude-plugin/marketplace.json +1 -1
- package/artifact/claude/.claude-plugin/plugin.json +1 -1
- package/artifact/claude/INSTALL.md +1 -1
- package/artifact/claude/bin/cargo-hauler-flight.mjs +758 -212
- package/artifact/claude/bin/cargo-hauler.mjs +1625 -673
- package/artifact/claude/hooks/event-route-session-start.mjs +2 -3
- package/artifact/claude/hooks/event-route-stop.mjs +2 -3
- package/artifact/claude/hooks/event-route-tool-after.mjs +2 -3
- package/artifact/claude/hooks/event-route-tool-before.mjs +2 -3
- package/artifact/claude/hooks/hooks-flight.mjs +653 -196
- package/artifact/claude/mcp/mcp-hauler-2b8242f9-flight.mjs +1087 -350
- package/artifact/claude/mcp/mcp-hauler-2b8242f9.mjs +867 -241
- package/artifact/claude/mcp-apps/dashboard.html +1 -1
- package/artifact/claude/scripts/hauler.mjs +1702 -699
- package/artifact/claude/skills/cargo-hauler/SKILL.md +15 -7
- package/artifact/claude/skills/hauler-dashboard/SKILL.md +1 -8
- package/artifact/codex/.codex-plugin/plugin.json +1 -1
- package/artifact/codex/INSTALL.md +1 -1
- package/artifact/codex/bin/cargo-hauler-flight.mjs +758 -212
- package/artifact/codex/bin/cargo-hauler.mjs +1625 -673
- package/artifact/codex/hooks/event-route-session-start.mjs +2 -3
- package/artifact/codex/hooks/event-route-stop.mjs +2 -3
- package/artifact/codex/hooks/event-route-tool-after.mjs +2 -3
- package/artifact/codex/hooks/event-route-tool-before.mjs +2 -3
- package/artifact/codex/hooks/hooks-flight.mjs +653 -196
- package/artifact/codex/mcp/mcp-hauler-2b8242f9-flight.mjs +1087 -350
- package/artifact/codex/mcp/mcp-hauler-2b8242f9.mjs +867 -241
- package/artifact/codex/mcp-apps/dashboard.html +1 -1
- package/artifact/codex/scripts/hauler.mjs +1702 -699
- package/artifact/codex/skills/cargo-hauler/SKILL.md +15 -7
- package/artifact/codex/skills/hauler-dashboard/SKILL.md +1 -8
- package/artifact/cursor/.cursor-plugin/plugin.json +1 -1
- package/artifact/cursor/INSTALL.md +1 -1
- package/artifact/cursor/bin/cargo-hauler-flight.mjs +758 -212
- package/artifact/cursor/bin/cargo-hauler.mjs +1625 -673
- package/artifact/cursor/hooks/event-route-session-start.mjs +2 -3
- package/artifact/cursor/hooks/event-route-stop.mjs +2 -3
- package/artifact/cursor/hooks/event-route-tool-after.mjs +2 -3
- package/artifact/cursor/hooks/event-route-tool-before.mjs +2 -3
- package/artifact/cursor/hooks/hooks-flight.mjs +653 -196
- package/artifact/cursor/install.mjs +1 -1
- package/artifact/cursor/mcp/mcp-hauler-2b8242f9-flight.mjs +1087 -350
- package/artifact/cursor/mcp/mcp-hauler-2b8242f9.mjs +867 -241
- package/artifact/cursor/mcp-apps/dashboard.html +1 -1
- package/artifact/cursor/scripts/hauler.mjs +1702 -699
- package/artifact/cursor/skills/cargo-hauler/SKILL.md +15 -7
- package/artifact/cursor/skills/hauler-dashboard/SKILL.md +1 -8
- package/artifact/portable/INSTALL.md +1 -1
- package/artifact/portable/bin/cargo-hauler-flight.mjs +758 -212
- package/artifact/portable/bin/cargo-hauler.mjs +1625 -673
- package/artifact/portable/install.mjs +1 -1
- package/artifact/portable/mcp/mcp-hauler-2b8242f9-flight.mjs +767 -212
- package/artifact/portable/mcp/mcp-hauler-2b8242f9.mjs +866 -239
- package/artifact/portable/mcp-apps/dashboard.html +1 -1
- package/artifact/portable/plugin.json +1 -1
- package/artifact/portable/scripts/hauler.mjs +1702 -699
- package/artifact/portable/skills/cargo-hauler/SKILL.md +15 -7
- package/artifact/portable/skills/hauler-dashboard/SKILL.md +1 -8
- package/dist/bin/cargo-hauler-flight.mjs +758 -212
- package/dist/bin/cargo-hauler.js +1625 -673
- package/dist/bin/hauler.js +1702 -699
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a2c39f3: Bump `agent-bundle` and `@agent-bundle/runtime` to the pkg.pr.new preview at `5775351fb`, which drops the Claude `plugin.json` `hooks` pointer so Claude Code no longer rejects the plugin with "Duplicate hooks file"
|
|
8
|
+
- ccd6a50: Start the auto-spawned daemon with a curated environment (`PATH`, `HOME`, `USER`, `LOGNAME`, `SHELL`, `TMPDIR`, `LANG`/`LC_*`, `XDG_*`, `CARGO_HOME`, `RUSTUP_HOME`, `SSL_CERT_*`, `*_proxy`, `CARGO_HAULER_*`) and the state directory as its cwd, so the first client's `RUSTFLAGS`, `CARGO_TARGET_DIR`, `RUSTC_WRAPPER`, or fd-based `MAKEFLAGS` no longer become the base of every other session's builds. `hauler exec` now relays SIGINT/SIGTERM: a brokered ticket is killed before the client exits `130`/`143`, a direct run's cargo process group is terminated instead of orphaned. A ticket that ends other than `done` prints `ticket cc-N <status>[: reason]` with `128 + signal` for signaled runs; a connection lost after the ack names the ticket (`hauler result cc-N`); a direct run prints its spawn error. The ack carries `waitEtaMs` and auto-background decides on queue wait plus runtime, shown as `wait ~Ns, run ~Ns`. A daemon that never accepted within 60 s goes straight to a direct run instead of a start attempt and a second cycle. `--cwd` is resolved against the caller. `CARGO_HAULER_CARGO_BIN` is read from the daemon's own environment. The PATH shim falls back to the real cargo when its hauler entry is gone and `install-shim` says to re-run `--force` after upgrades. Descriptor-based `MAKEFLAGS`/`MFLAGS`/`CARGO_MAKEFLAGS` jobservers are not transported. Heartbeats count from `started`; a failed or unconfirmed detach is reported; `hauler request --session` no longer holds the stop hook (matching `exec --bg`); `await`/`result`/`kill` fail fast on a daemon `error` reply (#55)
|
|
9
|
+
- ab91d88: Harden the daemon's socket and singleton-lock lifecycle. The daemon now listens on `daemon.sock.<pid>` and atomically renames it over `daemon.sock`, so a daemon that lost socket ownership no longer deletes its replacement's socket on the way out (previously both daemons died and their in-flight tickets were reaped as orphaned). `hauler daemon run` acquires the singleton lock before opening the ledger, running migrations, or draining the passthrough spool, so a losing instance touches neither; it re-checks a held lock about once a second for up to ~20 s instead of giving up on the first look, treats a lock written before the current boot as stale whatever its recorded pid, treats a pid it cannot signal (`EPERM`) as unknown rather than alive, and reclaims a stale lock atomically so two cold starters can no longer both start. A second Ctrl-C during shutdown is now swallowed instead of skipping finalizers and leaving the lock and socket behind (#50)
|
|
10
|
+
- d09b2fb: Harden the `tool/before` shell rewrite and its companions. The rewrite now leaves a command untouched when the pinned `bashjsast` parser cannot round-trip it — background `&`, the `time` keyword, `|&`, `coproc`, and a heredoc feeding a pipeline previously came back as a blocking, un-timed, or syntactically broken command. It no longer passes `--cwd`, so `cd crates/foo && cargo build` builds in `crates/foo`; it skips `command -v/-V cargo`, `type cargo`, and `which cargo`; wraps the unbrokered half of `hauler exec -- cargo build && cargo test`, `while ! cargo build`, and `rustup run <toolchain> -- cargo`. The `cargo clean` guard distinguishes a busy daemon (probe timeout → brokered so the lane serializes the clean) from an absent one (raw run). `tool/after` announces only background or detached tickets, never a foreground run the agent just watched. The stop route clamps `CARGO_HAULER_STOP_WAIT_MS` to the 2 h await ceiling, and `hook-state.json` is written atomically with per-session deny-counter pruning. (#56)
|
|
11
|
+
- df912ae: Honour `hauler kill` for a job parked at the admission gate or on the permit semaphore (it settles `killed` at once instead of blocking its lane until a permit frees), never fold a kill-requested queued job into a batch, clamp the queue ETA so an overrunning lane head no longer cancels queued work and count a head parked at the gate, and finish every settlement step (waiters, lane release, follower exits) even when a ledger write fails. Late attachers receive each replayed chunk exactly once, a follower whose leader exits during registration stays settled (attach/running ledger writes never reopen a terminal row), an early follower release can no longer surface as a `pump failed` cargo termination, and identity attach requires callers to agree on `mergeStderr`. When the shared jobserver FIFO is armed the daemon no longer sets `CARGO_BUILD_JOBS`; `CARGO_HAULER_JOBS_GRANT` applies only while the FIFO is unavailable. (#51, #52, #54)
|
|
12
|
+
- 3f70b60: Keep the daemon responsive and its storage bounded. Kache priors are refreshed without blocking the event loop: the `events.jsonl` tail is read asynchronously from a persisted byte offset and parsed in yielding slices, and the `index.db` aggregate is recomputed only when the file changes on disk. The ledger gains a startup retention pass — finished requests older than `CARGO_HAULER_LEDGER_RETENTION_DAYS` (default `30`) or beyond `CARGO_HAULER_LEDGER_MAX_ROWS` (default `50000`) are deleted with their transitions; `0` disables either limit — the attachment-savings backfill runs once per database (`PRAGMA user_version`) through the rowid index instead of on every open, and request/transition writes commit atomically. NDJSON client lines are capped at 16 MiB: an oversize line gets a `bad-message` error and the connection is closed. Numeric `CARGO_HAULER_*` overrides that do not parse or fall outside their range now log a warning and keep the default instead of silently disabling the arm; `0` or `off` still disables where documented, `CARGO_HAULER_MEM_PRESSURE_SOFT` must stay below `CARGO_HAULER_MEM_PRESSURE_HARD`, and `CARGO_HAULER_BATCH=false|off|no` disables batching like `0` (#57).
|
|
13
|
+
- dd778ab: Fold `cargo test` / `cargo nextest run` requests only when their test selection is identical — same `--test` targets, name filters, arguments after `--`, and nextest filterset — so a composite runs exactly what each participant asked for over the union of their packages (`cargo test -p a` + `cargo test -p b` → `cargo test -p a -p b --no-fail-fast`), never a foreign target or filter. When a composite fails, a folded participant inherits the failure only if it named every package the composite ran; otherwise it is requeued and runs alone instead of reporting another package's failure as its own. Unmodeled post-subcommand options that take a value (`-j`/`--jobs`, `--color`, `--message-format`, `-Z`, `--config`, and nextest's `--retries`, `--test-threads`, `-P`, …) now consume that value instead of recording it as a test-name filter (#53).
|
|
14
|
+
|
|
15
|
+
## 0.4.2
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 747f311: Remove `scripts/preview-dashboard.mjs`; preview the dashboard App through `agent-bundle dev` (the Workbench MCP page previews `ui://cargo-hauler/dashboard.html` over its bound session), and point the `hauler-dashboard` skill and README at it (#49)
|
|
20
|
+
- c5bfc4b: Add `hauler kill <ticket>` and the `hauler_kill` tool: a queued ticket is dropped, a running one has its cargo process group terminated (SIGTERM, then SIGKILL after `CARGO_HAULER_KILL_GRACE_MS`) and its lane freed, with riders settled by the daemon. The skill and session context now say to use it instead of killing cargo PIDs (#46). The `maxWaitMs` ceiling on `await` is reported in plain words rather than a raw validator payload (#47).
|
|
21
|
+
- 5f89f16: Merged-output runs (`cargo run 2>&1`, a shared terminal) strip cargo's captured color when the shared descriptor is not a color-capable TTY, matching direct cargo's `auto`, and the merge is honoured in passthrough runs too. `hauler await` / `hauler_await` subtract the time already spent fetching the ticket snapshot from the daemon wait, so the worst case stays under the render session. A failed run times its own intent for retries but no longer feeds the per-crate priors shared with other intents.
|
|
22
|
+
|
|
3
23
|
## 0.4.1
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -136,11 +136,12 @@ the same filter as its `session` field). Results carry
|
|
|
136
136
|
| `tool:hauler/hauler_last` · `cli:last` | most recent request | `LastDocument` |
|
|
137
137
|
| `tool:hauler/hauler_await` · `cli:await` | long-poll a ticket (≤ 2 h) | `AwaitStream` → `AwaitDocument` |
|
|
138
138
|
| `tool:hauler/hauler_result` · `cli:result` | one ticket, live tail while running | `ResultDocument` |
|
|
139
|
+
| `tool:hauler/hauler_kill` · `cli:kill` | stop a queued or running ticket | `KillDocument` |
|
|
139
140
|
| `tool:hauler/hauler_request` · `cli:request` | submit a background request | `RequestDocument` |
|
|
140
141
|
| `cli:daemon` | `run` / `start` / `stop` / `status` | plain JSON, exit code from the result |
|
|
141
142
|
| `event:session/start` | new session | daemon state and the no-kill rule as context |
|
|
142
|
-
| `event:tool/before` | shell tool about to run | rewrites `cargo …` to `hauler exec --session … --host … -- cargo …`; denies `cargo clean` during in-flight builds |
|
|
143
|
-
| `event:tool/after` | shell tool finished | injects finished-ticket results once per session |
|
|
143
|
+
| `event:tool/before` | shell tool about to run | rewrites `cargo …` to `hauler exec --session … --host … -- cargo …`; denies `cargo clean` during in-flight builds, brokers it while the daemon is too busy to answer |
|
|
144
|
+
| `event:tool/after` | shell tool finished | injects finished background-ticket results once per session |
|
|
144
145
|
| `event:stop` | agent stopping | holds the stop while a foreground ticket is pending (bounded, re-deniable) |
|
|
145
146
|
|
|
146
147
|
### Skills
|
|
@@ -215,19 +216,20 @@ executable beside it (`dist/bin/cargo-hauler.js` in the package,
|
|
|
215
216
|
|
|
216
217
|
| Command | Behavior |
|
|
217
218
|
| --- | --- |
|
|
218
|
-
| `hauler exec [--session ID] [--host HOST] [--cwd DIR] [--bg] -- <cargo …>` | Submit Cargo through the daemon and stream output; hooks rewrite commands to this form. |
|
|
219
|
+
| `hauler exec [--session ID] [--host HOST] [--cwd DIR] [--bg] -- <cargo …>` | Submit Cargo through the daemon and stream output; hooks rewrite commands to this form. A relative `--cwd` is resolved against the caller's directory. Exits with cargo's code; `130`/`143` after a SIGINT/SIGTERM (the ticket is killed first); `75` when auto-backgrounded. |
|
|
219
220
|
| `hauler status [--limit N] [--cwd DIR] [--session ID] [--lane KEY] [--ticket ID …] [--status S …] [--command-contains TEXT]` | Queue, active runs, lanes, admission, kache, optionally filtered. |
|
|
220
221
|
| `hauler log [--limit N]` | Recent requests from the ledger. |
|
|
221
222
|
| `hauler last` | The most recent request. |
|
|
222
223
|
| `hauler await <ticket> [--max-wait-ms N]` | Long-poll until the ticket finishes or the wait expires (default 30 s, ceiling 55 s per call — the rendered-route budget; call again to keep waiting). |
|
|
223
224
|
| `hauler result <ticket>` | A stored ticket; running tickets include a live output tail. |
|
|
225
|
+
| `hauler kill <ticket>` | Stop a ticket: drop it from the queue or SIGTERM (then SIGKILL) its cargo process group, freeing the lane. Riders return to their lane or fail with it. |
|
|
224
226
|
| `hauler request [--session ID] [--host HOST] [--cwd DIR] -- <cargo …>` | Submit a background request and return its ticket. |
|
|
225
227
|
| `hauler daemon <run\|start\|stop\|status>` | Manage the daemon lifecycle. |
|
|
226
228
|
| `hauler install-shim [--dir DIR] [--real-cargo PATH] [--force]` | Install the optional PATH shim. |
|
|
227
229
|
|
|
228
230
|
The `hauler` MCP server projects the same operations as `hauler_status`,
|
|
229
|
-
`hauler_log`, `hauler_last`, `hauler_await`, `hauler_result`,
|
|
230
|
-
`hauler_request`, with the same filters as the CLI.
|
|
231
|
+
`hauler_log`, `hauler_last`, `hauler_await`, `hauler_result`, `hauler_kill`,
|
|
232
|
+
and `hauler_request`, with the same filters as the CLI.
|
|
231
233
|
|
|
232
234
|
## Testing
|
|
233
235
|
|
|
@@ -262,9 +264,23 @@ The hook parses the shell command and rewrites each Cargo invocation to
|
|
|
262
264
|
`hauler exec --session … --host … -- cargo …`. It recognizes `cargo` behind an
|
|
263
265
|
absolute path (`~/.cargo/bin/cargo`), and behind the wrappers agents actually
|
|
264
266
|
use: `env -u VAR X=y cargo …`, `timeout 600 cargo …`,
|
|
265
|
-
`rustup run <toolchain> cargo …`, `stdbuf`, `nice`, `ionice`, `nohup`,
|
|
266
|
-
|
|
267
|
-
`
|
|
267
|
+
`rustup run <toolchain> [--] cargo …`, `stdbuf`, `nice`, `ionice`, `nohup`,
|
|
268
|
+
`/usr/bin/time`, `strace`, `sudo`, `xargs`, `command`, `exec`, `builtin`,
|
|
269
|
+
and a negated test (`while ! cargo build; do …`). Other `rustup`
|
|
270
|
+
subcommands, lookups (`command -v cargo`, `type cargo`, `which cargo`), and
|
|
271
|
+
already-wrapped invocations are left alone; in a partially wrapped list
|
|
272
|
+
(`hauler exec -- cargo build && cargo test`) only the unwrapped half is
|
|
273
|
+
rewritten. The rewrite never passes `--cwd`: the command runs in the same
|
|
274
|
+
shell, so `hauler exec` inherits the working directory and
|
|
275
|
+
`cd crates/foo && cargo build` builds in `crates/foo`.
|
|
276
|
+
|
|
277
|
+
Before rewriting, the hook checks that the parser can reproduce the original
|
|
278
|
+
command token for token. Constructs the pinned parser cannot round-trip —
|
|
279
|
+
a background `&` (`nohup cargo build … &`, `cargo build & pid=$!`), the
|
|
280
|
+
`time` keyword, `|&`, `coproc`, a heredoc that feeds a pipeline or is
|
|
281
|
+
followed by another statement, `elif`, and `function name { … }` — are left
|
|
282
|
+
untouched and run as plain Cargo rather than risk emitting a changed
|
|
283
|
+
command.
|
|
268
284
|
|
|
269
285
|
A lane is keyed by workspace root and resolved target directory. It runs one
|
|
270
286
|
job at a time. Different lanes may run concurrently after acquiring one of the
|
|
@@ -286,8 +302,17 @@ Each admitted leader starts one Cargo process. Identity, coverage, and folded
|
|
|
286
302
|
batch requests share that process and receive its streamed output. A failed
|
|
287
303
|
stronger compile does not satisfy a coverage or compile-batch attachment; the
|
|
288
304
|
attached request returns to its lane unless its required compilation units were
|
|
289
|
-
already observed as successful. Folded tests share the composite process
|
|
290
|
-
output
|
|
305
|
+
already observed as successful. Folded tests share the composite process and
|
|
306
|
+
output. `cargo test` / `cargo nextest run` requests fold only when their test
|
|
307
|
+
selection is identical — the same `--test` targets, name filters, arguments
|
|
308
|
+
after `--`, and nextest filterset — so only the package set differs:
|
|
309
|
+
`cargo test -p a` and `cargo test -p b` become
|
|
310
|
+
`cargo test -p a -p b --no-fail-fast`. On success every participant shares
|
|
311
|
+
the exit. When the composite fails, a participant inherits that failure only
|
|
312
|
+
if it named every package the composite ran; otherwise the failing tests may
|
|
313
|
+
belong to another participant's package, so it is requeued and runs alone
|
|
314
|
+
(cargo's test output does not attribute failures to packages). The leader
|
|
315
|
+
keeps the composite exit, as compile-batch leaders do.
|
|
291
316
|
|
|
292
317
|
Brokered output keeps cargo's stdout and stderr as separate channels. When the
|
|
293
318
|
caller's own stdout and stderr are the same open file (`cargo run 2>&1`, a
|
|
@@ -322,18 +347,22 @@ heartbeats. Non-compiling cargo subcommands (`fmt`, `update`, `fetch`, `add`,
|
|
|
322
347
|
`remove`, `generate-lockfile`, `vendor`, `new`, `init`, `info`, `uninstall`)
|
|
323
348
|
run locally instead of queueing for a permit.
|
|
324
349
|
|
|
325
|
-
The
|
|
326
|
-
across the configured permit count, with a floor of four jobs. Separately, the
|
|
327
|
-
daemon arms one GNU make jobserver FIFO with `cores - 1` tokens when it
|
|
350
|
+
The daemon arms one GNU make jobserver FIFO with `cores - 1` tokens when it
|
|
328
351
|
acquires the singleton lock and passes it to every Cargo it spawns through
|
|
329
352
|
`MAKEFLAGS`, so concurrent lanes share one global rustc parallelism budget.
|
|
353
|
+
While the FIFO is armed no `CARGO_BUILD_JOBS` is injected, because Cargo only
|
|
354
|
+
joins an inherited jobserver when `-j`/`build.jobs` is unset. The per-run
|
|
355
|
+
`CARGO_BUILD_JOBS` grant — the available cores divided across the configured
|
|
356
|
+
permit count, with a floor of four jobs — is the fallback for a daemon that
|
|
357
|
+
could not arm the FIFO (no `mkfifo`, unwritable state directory). A caller's
|
|
358
|
+
own `-j` flag or `CARGO_BUILD_JOBS` always wins over both.
|
|
330
359
|
|
|
331
360
|
| Capability | Behavior |
|
|
332
361
|
| --- | --- |
|
|
333
362
|
| Work sharing | Identical requests attach, covered checks attach, and compatible queued compile or test requests fold. |
|
|
334
363
|
| Lane isolation | A workspace-root and target-directory pair is serialized independently from other lanes. |
|
|
335
364
|
| Admission | Per-core load, Linux CPU PSI, Linux memory PSI and `MemAvailable`, macOS VM pressure, configured thresholds, and the global permit cap control new starts. |
|
|
336
|
-
| Parallelism |
|
|
365
|
+
| Parallelism | One daemon-owned jobserver FIFO shared by every spawned Cargo; a per-run `CARGO_BUILD_JOBS` grant only when the FIFO could not be armed. |
|
|
337
366
|
| Scheduling | EWMA estimates, optional kache priors, fan-out, dependency topology, recent edits, and request age determine lane order. |
|
|
338
367
|
| Persistence | Tickets, output tails, timings, outcomes, and savings are stored in SQLite. |
|
|
339
368
|
| Caller output and status | Output streams to attached callers; late callers receive buffered replay. After 30 seconds without output, the client emits a progress heartbeat every 15 seconds with lane queue position, the lane-head ticket, and an aggregate wait ETA. |
|
|
@@ -349,19 +378,37 @@ immediately. A synchronous request also switches to background mode when a
|
|
|
349
378
|
*measured* estimate — EWMA history or kache priors, never the cold-start
|
|
350
379
|
default — exceeds the host's shell-tool cap (nine minutes for Claude, ten for
|
|
351
380
|
Codex, fourteen for Cursor; the PATH shim uses `CARGO_HAULER_HOST` when it is
|
|
352
|
-
exported, otherwise the Claude cap).
|
|
353
|
-
|
|
354
|
-
|
|
381
|
+
exported, otherwise the Claude cap). The estimate that is compared is the
|
|
382
|
+
whole wait: the work queued ahead in the lane plus the job's own runtime,
|
|
383
|
+
which the queued line reports as `wait ~Ns, run ~Ns`. That conversion exits
|
|
384
|
+
`75` (`EX_TEMPFAIL`) with the ticket on stderr, so `cargo build && …` chains
|
|
385
|
+
and scripts cannot mistake "submitted" for "built"; explicit `--bg` keeps exit
|
|
355
386
|
`0`. Failed runs feed the estimate history too, so a broken build is not
|
|
356
387
|
re-estimated cold on every retry.
|
|
357
388
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
389
|
+
A foreground `hauler exec` that receives SIGINT or SIGTERM (Ctrl-C, or a
|
|
390
|
+
`timeout N …` wrapper) asks the daemon to kill its ticket, waits for the
|
|
391
|
+
answer, and exits `130` or `143`; in a direct run it terminates the cargo
|
|
392
|
+
process group the same way. A ticket that ends other than `done` is reported
|
|
393
|
+
on stderr as `ticket cc-N <status>[ (signal)][: reason]`, and its exit code is
|
|
394
|
+
cargo's, `128 + signal` for a signaled run, or `1` when the daemon could not
|
|
395
|
+
start cargo at all. If the connection drops after the ticket was accepted, the
|
|
396
|
+
client prints `connection to daemon lost; ticket cc-N continues — hauler
|
|
397
|
+
result cc-N` and exits `1`; the daemon finishes the ticket on its own.
|
|
398
|
+
|
|
399
|
+
The `tool/after` route checks the session's background tickets — `--bg`,
|
|
400
|
+
`hauler_request`, and synchronous requests the client converted to a ticket
|
|
401
|
+
— and, on the first tool call after one finishes, adds its result to the
|
|
402
|
+
agent context. A foreground ticket streamed its exit to the shell the agent
|
|
403
|
+
just watched, so it is never re-announced. For foreground tickets, the
|
|
404
|
+
`stop` route waits for the lower of the remaining estimate and
|
|
405
|
+
`CARGO_HAULER_STOP_WAIT_MS` (clamped to the daemon's two-hour await
|
|
406
|
+
ceiling); if the ticket finishes it denies the stop and returns the result,
|
|
407
|
+
otherwise it denies with status and ETA. `stopHookActive` and an
|
|
408
|
+
eight-denial cap per ticket prevent a repeated stop loop; the per-ticket
|
|
409
|
+
counters live in `hook-state.json`, written atomically and pruned once a
|
|
410
|
+
session's tickets are no longer pending. `--bg` tickets never hold a stop.
|
|
411
|
+
Codex 0.147.0 stop-hold behaviour is verified in
|
|
365
412
|
[docs/codex-hooks.md](docs/codex-hooks.md).
|
|
366
413
|
|
|
367
414
|
### PATH shim
|
|
@@ -373,7 +420,11 @@ When the daemon starts Cargo it sets `CARGO_HAULER_INSIDE=1`, and the shim then
|
|
|
373
420
|
invokes the embedded Cargo directly, so the daemon's own Cargo never returns
|
|
374
421
|
through the broker. The shim is POSIX-only; its directory must appear before
|
|
375
422
|
rustup's Cargo directory on `PATH`; replacing an existing destination requires
|
|
376
|
-
`--force`.
|
|
423
|
+
`--force`. The embedded `hauler` entry lives in a versioned plugin directory:
|
|
424
|
+
when that file no longer exists (an upgrade replaced the directory), the shim
|
|
425
|
+
runs the embedded Cargo directly instead of failing, and `install-shim` says
|
|
426
|
+
so — re-run `hauler install-shim --force` after such an upgrade to route
|
|
427
|
+
scripted Cargo through the broker again.
|
|
377
428
|
|
|
378
429
|
### Caller environment
|
|
379
430
|
|
|
@@ -386,10 +437,24 @@ Cargo, so `FOO=bar cargo build` reaches `build.rs`, `env!()`, `cargo run`, and
|
|
|
386
437
|
for coalescing is digested from the build-relevant subset only (`CARGO_*`,
|
|
387
438
|
`RUST*`, `CC`/`CXX`/`AR`/`CFLAGS`/`CXXFLAGS`/`LDFLAGS` with target-suffixed
|
|
388
439
|
forms, and `PKG_CONFIG_PATH`); pass knobs a `build.rs` reads through
|
|
389
|
-
`--config 'env.FOO="bar"'` when they must also split identity.
|
|
440
|
+
`--config 'env.FOO="bar"'` when they must also split identity. One value is
|
|
441
|
+
filtered rather than forwarded: a `MAKEFLAGS`, `MFLAGS`, or `CARGO_MAKEFLAGS`
|
|
442
|
+
carrying a descriptor-based jobserver (`--jobserver-auth=R,W`,
|
|
443
|
+
`--jobserver-fds=R,W`) names file descriptors that exist only in the caller,
|
|
444
|
+
so it is dropped and the daemon's shared FIFO jobserver applies; a
|
|
445
|
+
`fifo:PATH` jobserver travels as-is.
|
|
390
446
|
`hauler request` and `hauler_request` submit without a caller environment;
|
|
391
447
|
their Cargo processes run with the daemon's environment.
|
|
392
448
|
|
|
449
|
+
The daemon's own environment is deliberately small. When a client starts it,
|
|
450
|
+
the daemon receives only `PATH`, `HOME`, `USER`, `LOGNAME`, `SHELL`, `TMPDIR`,
|
|
451
|
+
`LANG` and `LC_*`, `XDG_*`, `CARGO_HOME`, `RUSTUP_HOME`, `SSL_CERT_*`, the
|
|
452
|
+
`*_proxy` variables, and every `CARGO_HAULER_*` setting, with the state
|
|
453
|
+
directory as its working directory. The starting shell's `RUSTFLAGS`,
|
|
454
|
+
`CARGO_TARGET_DIR`, `RUSTC_WRAPPER`, `CARGO_BUILD_*`, `MAKEFLAGS`, `CC`, and
|
|
455
|
+
similar build knobs are not inherited, so they cannot become the silent base of
|
|
456
|
+
every other session's builds.
|
|
457
|
+
|
|
393
458
|
### Kache integration
|
|
394
459
|
|
|
395
460
|
When [kache](https://github.com/ScriptedAlchemy/kache) is available,
|
|
@@ -405,27 +470,32 @@ is reported as unavailable and never rejects a request.
|
|
|
405
470
|
| Variable | Default | Meaning |
|
|
406
471
|
| --- | --- | --- |
|
|
407
472
|
| `CARGO_HAULER_STATE_DIR` | Per-user cache directory | Unix socket or Windows named pipe source, SQLite ledger, daemon log, pid lock, `hook-state.json`, and `hook-events.jsonl`. No legacy alias. |
|
|
408
|
-
| `CARGO_HAULER_CARGO_BIN` | `$CARGO_HOME/bin/cargo` | Cargo binary for daemon-started work; bare `cargo` is the last fallback. Never resolved through `PATH`. |
|
|
409
|
-
| `CARGO_HAULER_MAX_CONCURRENT` | `5` | Global admission permits for Cargo processes across all lanes. |
|
|
410
|
-
| `CARGO_HAULER_JOBS_GRANT` | `max(4, cores / max concurrent)` | `CARGO_BUILD_JOBS` added to each Cargo process; `0` disables injection. |
|
|
473
|
+
| `CARGO_HAULER_CARGO_BIN` | `$CARGO_HOME/bin/cargo` | Cargo binary for daemon-started work; bare `cargo` is the last fallback. Never resolved through `PATH`. Read from the daemon's own environment (export it where the daemon starts, or before `hauler daemon start`); clients do not forward it. |
|
|
474
|
+
| `CARGO_HAULER_MAX_CONCURRENT` | `5` | Global admission permits for Cargo processes across all lanes; an integer >= 1. |
|
|
475
|
+
| `CARGO_HAULER_JOBS_GRANT` | `max(4, cores / max concurrent)` | `CARGO_BUILD_JOBS` added to each Cargo process only while the shared jobserver FIFO is not armed; an armed daemon injects `MAKEFLAGS` instead and leaves `CARGO_BUILD_JOBS` unset. `0` disables injection. |
|
|
411
476
|
| `CARGO_HAULER_LOAD_THRESHOLD` | Disabled | Per-core one-minute load threshold for deferring new admissions. |
|
|
412
477
|
| `CARGO_HAULER_LOAD_MIN` | `2` | Active Cargo processes below which load, CPU PSI, and soft memory pressure do not defer admission. |
|
|
413
|
-
| `CARGO_HAULER_CPU_PRESSURE_THRESHOLD` | `75` | Linux CPU PSI `some avg10` percentage for deferring new admissions; `0` disables. |
|
|
414
|
-
| `CARGO_HAULER_MEM_PRESSURE_SOFT` | `10` (Linux) | Memory PSI `full avg10` percentage for soft deferral; `0` disables. |
|
|
415
|
-
| `CARGO_HAULER_MEM_PRESSURE_HARD` | `20` (Linux) | Memory PSI `full avg10` percentage for hard deferral, confirmed by `full avg60` at half the value; `0` disables. |
|
|
416
|
-
| `CARGO_HAULER_MEM_AVAILABLE_MIN_GB` | `8` (Linux) | `MemAvailable` floor in GiB for hard deferral; `0` disables. |
|
|
417
|
-
| `CARGO_HAULER_MEM_PRESSURE_LEVEL` | `2` (macOS) | Kernel VM pressure level that starts soft deferral (`2` warn, `4` critical). |
|
|
478
|
+
| `CARGO_HAULER_CPU_PRESSURE_THRESHOLD` | `75` | Linux CPU PSI `some avg10` percentage for deferring new admissions; `0` or `off` disables. |
|
|
479
|
+
| `CARGO_HAULER_MEM_PRESSURE_SOFT` | `10` (Linux) | Memory PSI `full avg10` percentage for soft deferral; `0` or `off` disables. Must stay below the hard threshold, otherwise both revert to their defaults. |
|
|
480
|
+
| `CARGO_HAULER_MEM_PRESSURE_HARD` | `20` (Linux) | Memory PSI `full avg10` percentage for hard deferral, confirmed by `full avg60` at half the value; `0` or `off` disables. |
|
|
481
|
+
| `CARGO_HAULER_MEM_AVAILABLE_MIN_GB` | `8` (Linux) | `MemAvailable` floor in GiB for hard deferral; `0` or `off` disables. |
|
|
482
|
+
| `CARGO_HAULER_MEM_PRESSURE_LEVEL` | `2` (macOS) | Kernel VM pressure level that starts soft deferral (`2` warn, `4` critical); `0` or `off` disables. |
|
|
418
483
|
| `CARGO_HAULER_HEAVY_MEM_AVAILABLE_GB` | `16` (Linux) | `MemAvailable` in GiB below which concurrent heavy leaders (release/perf/bench profiles, workspace-wide runs) are capped; `0` or `off` disables the cap. |
|
|
419
484
|
| `CARGO_HAULER_HEAVY_MAX_CONCURRENT` | `1` | Heavy leaders admitted at once while the cap is active. |
|
|
420
485
|
| `CARGO_HAULER_REPLAY_BUFFER_BYTES` | `4194304` | Leader output retained in memory for late-attacher replay. |
|
|
421
486
|
| `CARGO_HAULER_KACHE_INDEX` | kache's configured store | kache index for per-crate timing priors; an empty string disables it. |
|
|
422
|
-
| `CARGO_HAULER_BATCH` | Enabled | `0` disables the batch composer. |
|
|
487
|
+
| `CARGO_HAULER_BATCH` | Enabled | `0`, `false`, `off`, or `no` disables the batch composer. |
|
|
423
488
|
| `CARGO_HAULER_BATCH_WINDOW_MS` | `150` | Delay applied to a batchable lane head so nearby requests can fold; `0` disables. |
|
|
424
489
|
| `CARGO_HAULER_KILL_GRACE_MS` | `8000` | Time between SIGTERM and SIGKILL when the daemon stops a Cargo process. |
|
|
425
|
-
| `CARGO_HAULER_STOP_WAIT_MS` | `30000` | Maximum wait for one stop-hook invocation. |
|
|
490
|
+
| `CARGO_HAULER_STOP_WAIT_MS` | `30000` | Maximum wait for one stop-hook invocation; values above the 7200000 ms await ceiling are clamped. |
|
|
491
|
+
| `CARGO_HAULER_LEDGER_RETENTION_DAYS` | `30` | Finished ledger rows older than this many days are deleted when the daemon starts; `0` disables the age limit. |
|
|
492
|
+
| `CARGO_HAULER_LEDGER_MAX_ROWS` | `50000` | Total ledger rows beyond which the oldest finished rows are deleted when the daemon starts; `0` disables the row cap. |
|
|
426
493
|
| `CARGO_HAULER_LOG_LEVEL` | `Info` | Daemon log level. |
|
|
427
494
|
| `CARGO_HAULER_HOST`, `CARGO_HAULER_SESSION` | Unset | Default `--host` and `--session` attribution for `hauler exec`; the PATH shim also borrows `CARGO_HAULER_HOST`'s shell cap for auto-background. |
|
|
428
495
|
|
|
496
|
+
A numeric value that does not parse or falls outside its range is reported
|
|
497
|
+
as a warning (daemon log, or stderr for hand-run commands) and the default
|
|
498
|
+
applies; only `0` or `off` disables an arm that documents that contract.
|
|
429
499
|
Each `CARGO_HAULER_*` tuning value takes precedence over its retained legacy
|
|
430
500
|
`CARGO_CONDUCTOR_*` alias; `CARGO_CONDUCTOR_STATE_DIR` is ignored and hand-run
|
|
431
501
|
commands warn when it is still exported (see
|
|
@@ -443,7 +513,8 @@ unset, the daemon reads kache's configured local store from
|
|
|
443
513
|
command through, and a client that cannot reach the daemon makes one
|
|
444
514
|
auto-start attempt and then invokes Cargo directly. A daemon that is alive
|
|
445
515
|
but too loaded to accept within 2 seconds is not treated as absent: `exec`
|
|
446
|
-
retries for up to 60 seconds
|
|
516
|
+
retries for up to 60 seconds, then runs Cargo directly without a start
|
|
517
|
+
attempt or a second retry cycle.
|
|
447
518
|
- The plugin's own documents never fail open: `hauler_result` and
|
|
448
519
|
`hauler_await` fail loudly when the daemon is unreachable instead of
|
|
449
520
|
reporting a ticket as not found; `hauler_status`, `hauler_log`, and
|
|
@@ -452,6 +523,11 @@ unset, the daemon reads kache's configured local store from
|
|
|
452
523
|
- Test sharing uses identity attachment or batch folding, never coverage.
|
|
453
524
|
Folded `test` and `nextest` requests receive the composite output and exit
|
|
454
525
|
code, so a failure may come from another package in the batch.
|
|
526
|
+
- The `cargo clean` guard probes the daemon for 250 ms. Active work denies
|
|
527
|
+
the clean; an idle daemon brokers it; a daemon that accepts but does not
|
|
528
|
+
answer in time is busy, so the clean is brokered and the lane serializes
|
|
529
|
+
it behind the builds it would otherwise race; only a socket nobody listens
|
|
530
|
+
on (`ECONNREFUSED`, `ENOENT`) lets a raw `cargo clean` run.
|
|
455
531
|
- Hook rewrites, policy denials such as `cargo clean` during an active build,
|
|
456
532
|
and malformed requests are recorded (`hook-events.jsonl`; a failed ledger
|
|
457
533
|
row).
|
|
@@ -467,12 +543,16 @@ pnpm run build # artifact/{claude,codex,cursor,portable} and dist/bin
|
|
|
467
543
|
pnpm run inspect # per-host component accounting
|
|
468
544
|
pnpm run doctor # installed copies versus the artifact
|
|
469
545
|
pnpm run check # the gate
|
|
470
|
-
node scripts/preview-dashboard.mjs --port 4941 # dashboard outside an MCP host
|
|
471
546
|
```
|
|
472
547
|
|
|
548
|
+
To see the dashboard outside an MCP host, run `pnpm run dev` and open the
|
|
549
|
+
Workbench's MCP page: it binds a session to the generated `hauler` server and
|
|
550
|
+
previews the `ui://cargo-hauler/dashboard.html` App over that session, so the
|
|
551
|
+
data is the daemon's own. The repository ships no preview harness of its own.
|
|
552
|
+
|
|
473
553
|
agent-bundle does not yet have an npm release; this repository pins the
|
|
474
554
|
[pkg.pr.new](https://pkg.pr.new) preview of main commit
|
|
475
|
-
[`
|
|
555
|
+
[`5775351fb`](https://github.com/ScriptedAlchemy/agent-bundle/commit/5775351fbc1c82e2861a9d69cad78adb086d052d)
|
|
476
556
|
for both `agent-bundle` and `@agent-bundle/runtime`. `inspect` reports the
|
|
477
557
|
`agent` component kind as unavailable on every host (agent-bundle G5
|
|
478
558
|
deferral); this plugin defines no agents. Two framework limitations observed
|