cargo-hauler 0.5.0 → 0.6.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 +27 -0
- package/README.md +127 -26
- package/artifact/agent-bundle.hooks.json +1 -1
- 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 +631 -40
- package/artifact/claude/bin/cargo-hauler.mjs +2046 -319
- package/artifact/claude/hooks/after-tool-shell-after-264de610.mjs +1162 -0
- package/artifact/claude/hooks/before-tool-shell-before-9212219e.mjs +4021 -0
- package/artifact/claude/hooks/event-route-session-start.mjs +2 -2
- package/artifact/claude/hooks/event-route-stop.mjs +2 -2
- package/artifact/claude/hooks/hooks-flight.mjs +166 -3426
- package/artifact/claude/hooks/hooks.json +1 -1
- package/artifact/claude/mcp/mcp-hauler-2b8242f9-flight.mjs +766 -3382
- package/artifact/claude/mcp/mcp-hauler-2b8242f9.mjs +634 -43
- package/artifact/claude/mcp-apps/dashboard.html +54 -6
- package/artifact/claude/scripts/hauler.mjs +1891 -335
- package/artifact/claude/skills/cargo-hauler/SKILL.md +19 -9
- package/artifact/claude/skills/hauler-dashboard/SKILL.md +4 -2
- package/artifact/codex/.codex-plugin/plugin.json +1 -1
- package/artifact/codex/INSTALL.md +1 -1
- package/artifact/codex/bin/cargo-hauler-flight.mjs +631 -40
- package/artifact/codex/bin/cargo-hauler.mjs +2046 -319
- package/artifact/codex/hooks/after-tool-shell-after-264de610.mjs +1167 -0
- package/artifact/codex/hooks/before-tool-shell-before-9212219e.mjs +4026 -0
- package/artifact/codex/hooks/event-route-session-start.mjs +2 -2
- package/artifact/codex/hooks/event-route-stop.mjs +2 -2
- package/artifact/codex/hooks/hooks-flight.mjs +166 -3426
- package/artifact/codex/hooks/hooks.json +1 -1
- package/artifact/codex/mcp/mcp-hauler-2b8242f9-flight.mjs +766 -3382
- package/artifact/codex/mcp/mcp-hauler-2b8242f9.mjs +634 -43
- package/artifact/codex/mcp-apps/dashboard.html +54 -6
- package/artifact/codex/scripts/hauler.mjs +1891 -335
- package/artifact/codex/skills/cargo-hauler/SKILL.md +19 -9
- package/artifact/codex/skills/hauler-dashboard/SKILL.md +4 -2
- package/artifact/cursor/.cursor-plugin/plugin.json +1 -1
- package/artifact/cursor/INSTALL.md +1 -1
- package/artifact/cursor/bin/cargo-hauler-flight.mjs +631 -40
- package/artifact/cursor/bin/cargo-hauler.mjs +2046 -319
- package/artifact/cursor/hooks/after-tool-shell-after-264de610.mjs +1204 -0
- package/artifact/cursor/hooks/before-tool-shell-before-9212219e.mjs +4063 -0
- package/artifact/cursor/hooks/event-route-session-start.mjs +2 -2
- package/artifact/cursor/hooks/event-route-stop.mjs +2 -2
- package/artifact/cursor/hooks/hooks-flight.mjs +166 -3426
- package/artifact/cursor/hooks/hooks.json +1 -1
- package/artifact/cursor/install.mjs +1 -1
- package/artifact/cursor/mcp/mcp-hauler-2b8242f9-flight.mjs +766 -3382
- package/artifact/cursor/mcp/mcp-hauler-2b8242f9.mjs +634 -43
- package/artifact/cursor/mcp-apps/dashboard.html +54 -6
- package/artifact/cursor/scripts/hauler.mjs +1891 -335
- package/artifact/cursor/skills/cargo-hauler/SKILL.md +19 -9
- package/artifact/cursor/skills/hauler-dashboard/SKILL.md +4 -2
- package/artifact/portable/INSTALL.md +1 -1
- package/artifact/portable/bin/cargo-hauler-flight.mjs +631 -40
- package/artifact/portable/bin/cargo-hauler.mjs +2046 -319
- package/artifact/portable/install.mjs +1 -1
- package/artifact/portable/mcp/mcp-hauler-2b8242f9-flight.mjs +632 -41
- package/artifact/portable/mcp/mcp-hauler-2b8242f9.mjs +633 -42
- package/artifact/portable/mcp-apps/dashboard.html +54 -6
- package/artifact/portable/plugin.json +1 -1
- package/artifact/portable/scripts/hauler.mjs +1891 -335
- package/artifact/portable/skills/cargo-hauler/SKILL.md +19 -9
- package/artifact/portable/skills/hauler-dashboard/SKILL.md +4 -2
- package/dist/bin/cargo-hauler-flight.mjs +631 -40
- package/dist/bin/cargo-hauler.js +2046 -319
- package/dist/bin/hauler.js +1891 -335
- package/package.json +1 -1
- package/artifact/claude/hooks/event-route-tool-after.mjs +0 -106595
- package/artifact/claude/hooks/event-route-tool-before.mjs +0 -106595
- package/artifact/codex/hooks/event-route-tool-after.mjs +0 -106595
- package/artifact/codex/hooks/event-route-tool-before.mjs +0 -106595
- package/artifact/cursor/hooks/event-route-tool-after.mjs +0 -106595
- package/artifact/cursor/hooks/event-route-tool-before.mjs +0 -106595
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- cfb8d3c: Make coverage attachment fire in practice, say why it did not, and let a compile-only `cargo test --no-run` ride a running `cargo test`.
|
|
8
|
+
|
|
9
|
+
- A `test --no-run` / `bench --no-run` submitted while a `test` / `bench` with the same packages, target selection, features, and profile is in flight in the lane attaches to it and is released — `done`, exit 0, with the leader's compile time so far as its saved compute — the moment the leader prints Cargo's `Finished` line, while the leader goes on running its tests. The leader's test filters, `--test-threads`, and `--no-fail-fast` do not disqualify the rider; a `--lib` rider never attaches to a `--test foo` leader, or a named-target rider to a default-target one. A leader whose build fails requeues the rider through the existing failed-stronger path; a leader that never reports its build finished (`--quiet`, `CARGO_HAULER_OVERLAP_EXECUTION=0`) releases it at settlement. A rider that arrives after the leader's build already finished is refused (`leader-build-finished`) and runs its own, mostly fresh, cargo at once. Both rows carry `buildFinishedAtMs`. (#88)
|
|
10
|
+
- Coverage gates widened where safe: flags the daemon does not model (`--locked`, `--offline`, `--message-format`, …) and arguments after `--` disqualify a `check` riding a `build` / `check` only when they differ between the two requests; `check --tests` rides `build --tests` / `build --all-targets`; a `--bin X` / `--example X` rider rides a `--bins` / `--examples` leader. `check` still never rides `clippy` (a lint failure would misreport the check), a `-p` rider is still not proven by a leader compiling its unnamed default package set, and `--tests` does not stand in for a named `--test X`. (#89)
|
|
11
|
+
- Every refused attachment is logged at debug level (`attach rejected`) with the gate — `subcommand`, `opaque-arguments`, `passthrough`, `compile-surface`, `packages`, `targets`, `channels`, `leader-build-finished` — a one-line detail, and both tickets; the status report counts one refusal per request under `metrics.attach_rejections`, keyed by the nearest gate any leader reached (`hauler status --json`, `hauler_status`). (#89)
|
|
12
|
+
- d5ae3e9: Show what the hauler costs, not only what it saves: queue wait vs run time per metrics window, the compile/execute split per command, and kache store pressure. (#92)
|
|
13
|
+
|
|
14
|
+
- Each metrics window (`1h`, `24h`, `all`) now reports total queue wait against total run time for leaders, and splits the wait by cause: *lane-bound* (a same-lane leader was still compiling — before its `Finished` line or exit), *permit-bound* (every admission permit was held and no same-lane compile was to blame), and *other* (admission holds, `--after` prerequisites, scheduling latency). The classification is a pure sweep over ledger rows, run against the daemon's current permit count; the tile states that assumption, since runs admitted under an earlier cap are classified against today's.
|
|
15
|
+
- With `buildFinishedAtMs` on the row, the by-command split adds compile vs execution p50s for `test`/`run`/`bench` leaders, and the window reports the lane time the execution-phase hand-back released. Pure compiles have no split and say nothing.
|
|
16
|
+
- The kache panel (`hauler status`, `hauler_status`, and the dashboard) surfaces store pressure: blob bytes recorded in the index against `local_max_size` (from `KACHE_MAX_SIZE` or kache's `config.toml`; "limit unknown" names why when it cannot be read), the last GC from `gc_stats.json` — when, how long, what it evicted — with any `gc: skipping eviction` warnings matched from kache's `auto-gc.log`/`daemon.log` during that run, and `key_ms` mean/p95 over the events tail. Warnings appear when the store is over its limit or the last GC declined or skipped evictions. Missing or unparsable files render as unavailable with their reason, never as an empty store.
|
|
17
|
+
- Protocol additions are optional fields (`metrics.windows[].waitSplit`, `handBack`, `runTotalMs`, `waitTotalMs`, `bySubcommand[].phases`, `kache.pressure`); clients reading an older daemon see the tiles as unavailable rather than as zeros.
|
|
18
|
+
- ec9bf65: Fold more queued work into one Cargo run: compile batches with a `--` trailer, and test runs whose filters differ.
|
|
19
|
+
|
|
20
|
+
- `cargo build` / `check` / `clippy` requests with the same arguments after `--` now batch: `cargo clippy -p a -- -D warnings` and `cargo clippy -p b -- -D warnings` run as `cargo clippy -p a -p b -- -D warnings`, the trailer once. Such runs are demultiplexed like any other compile (the `--message-format` rewrite goes before the `--`), so under `-D warnings` a participant whose own units compiled cleanly is released as done when another participant's warnings fail the composite; the rest rerun alone. Trailers that differ, or a trailer on one side only, still keep the runs apart, as do differing `--all-targets` / `--tests` / `--features`. (#86)
|
|
21
|
+
- `cargo test` requests naming different packages fold even when their bare name filters differ: `cargo test -p a -- f1` and `cargo test -p b -- f2` run as `cargo test -p a -p b --no-fail-fast -- f1 f2`, the union of packages with the union of filters. An identical set of `--test-threads=N`, `--nocapture`, `--quiet` (or `-q`) after `--` folds too, carried once from the leader; a mismatched set does not, and `--exact`, `--skip`, `--ignored`, `--include-ignored`, `--list`, `--format`, `--logfile`, or any other harness flag keeps a run out of composites. `--lib` runs fold like `--test NAME` runs; the target selection still has to match. Requests naming the same packages still need the same filters, and unfiltered runs fold only with unfiltered runs. A folded participant inherits a composite failure only when it asked for every package and every filter the composite ran; otherwise it reruns alone. (#87)
|
|
22
|
+
- c27662d: Estimate compile and execute separately, and keep a realistic queue ETA when a live head overruns.
|
|
23
|
+
|
|
24
|
+
`hauler status` reports `compileEstimateMs`, `executeEstimateMs`, and the current `phase` on a ticket. Followers waiting on a `test`/`nextest`/`bench`/`run` head see only the remaining execution time in `queue.waitEtaMs` (zero once overlap has handed the lane back), and queued tickets ahead of them count only their compile when overlap will hand the lane back. A head that has passed `CARGO_HAULER_STALL_ESTIMATE_FACTOR` times its estimate but is still burning CPU or printing is flagged `estimateState: overrun` with the intent's p90 (`p90Ms`), and its followers see `queue.headEstimateState: overrun`; its remaining time is re-estimated from that p90 — never less than one more estimate's worth — instead of contributing zero to the queue. A silent no-CPU head stays `stalled`. Filtered `--test <name>` runs with no history of their own borrow a same-package neighbor's timing before falling back to the crate-wide prior.
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- f01833d: Cut what the hooks cost every shell tool call. The `tool/before` and `tool/after` hooks are now declared handlers compiled into standalone entries (`hooks/before-tool-shell-before-*.mjs`, `hooks/after-tool-shell-after-*.mjs`) instead of event routes that loaded the whole rendering runtime before looking at the command. `tool/before` reads `tool_input.command` and answers `continue` at once when it has no `cargo`, `hauler`, or `conductor` word (word-boundary aware; `mycargo` and `CARGO_HOME=… ls` do not match, `~/.cargo/bin/cargo`, `cargo-hauler`, and `echo cargo` do — a doubtful command takes the full path). `tool/after` runs that test plus one bounded 500 ms socket ping (`session-completed` with the session's hook-state cursor, no Effect runtime), and loads the telemetry and notification code only when the command was cargo-related or the daemon reported finished tickets; with no daemon it exits quietly with no output. Rewrite, `cargo clean` denial, `hauler exec` pass-through, and completion notices are unchanged. Measured with `/usr/bin/time -v`, median of 5, Claude `ls -la` envelope: `tool/before` 0.05 s / 48 MB and `tool/after` 0.05 s / 49 MB, against 0.54 s / 143 MB and 0.56 s / 144 MB for the 0.5.0 wrappers without a shared runtime (0.10 s / 64 MB with one, per #90); a `cargo test -p foo` envelope is 0.05 s / 49 MB either way. (#90)
|
|
29
|
+
|
|
3
30
|
## 0.5.0
|
|
4
31
|
|
|
5
32
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -130,6 +130,23 @@ followed by another statement, `elif`, and `function name { … }` — are left
|
|
|
130
130
|
untouched and run as plain Cargo rather than risk emitting a changed
|
|
131
131
|
command.
|
|
132
132
|
|
|
133
|
+
Both shell hooks run on every shell tool call, so they decide cheaply before
|
|
134
|
+
they do anything else. The `tool/before` entry reads `tool_input.command` and
|
|
135
|
+
answers `continue` for a command with no `cargo`, `hauler`, or `conductor`
|
|
136
|
+
word in it (word-boundary aware: `mycargo` and `CARGO_HOME=… ls` are not
|
|
137
|
+
matches, `~/.cargo/bin/cargo`, `cargo-hauler`, and `echo cargo` are — a false
|
|
138
|
+
negative would bypass the broker, so anything that looks like a mention takes
|
|
139
|
+
the full path). Only a matching command evaluates the parser and the rewrite.
|
|
140
|
+
The `tool/after` entry runs the token test and one bounded socket ping to the
|
|
141
|
+
daemon (the `session-completed` request with the session's hook-state cursor,
|
|
142
|
+
500 ms, no Effect runtime); it loads the telemetry and notification code only
|
|
143
|
+
when the command was cargo-related or the daemon reported finished tickets. A
|
|
144
|
+
non-cargo call with nothing finished — or no daemon at all — exits with no
|
|
145
|
+
output. Measured with `/usr/bin/time -v` on a Claude `ls -la` envelope, the
|
|
146
|
+
compiled entries take ~50 ms wall and ~49 MB RSS, against ~100 ms and 64 MB
|
|
147
|
+
for the 0.4.8 event-route wrappers with a shared runtime available and
|
|
148
|
+
~560 ms and 144 MB without one.
|
|
149
|
+
|
|
133
150
|
A lane is keyed by workspace root and resolved target directory. It compiles
|
|
134
151
|
one job at a time — Cargo's own build-directory lock would serialize them
|
|
135
152
|
anyway. Once a `test`, `nextest`, `bench`, or `run` leader reports its build
|
|
@@ -149,23 +166,53 @@ Within a lane, the daemon can reduce work in three ways:
|
|
|
149
166
|
1. **Identity attachment:** a byte-identical request attaches to an in-flight
|
|
150
167
|
run.
|
|
151
168
|
2. **Coverage attachment:** a narrower `check` attaches to a compatible
|
|
152
|
-
`build` or `check` that covers its package and target scope
|
|
169
|
+
`build` or `check` that covers its package and target scope (`--tests`
|
|
170
|
+
under `--tests` or `--all-targets` included); a compile-only
|
|
171
|
+
`test --no-run` / `bench --no-run` attaches to a running `test` / `bench`
|
|
172
|
+
with the same packages, target selection, features, and profile — the
|
|
173
|
+
leader's test filters and `--test-threads` select what runs, not what
|
|
174
|
+
compiles — and is released as soon as the leader prints its `Finished`
|
|
175
|
+
line rather than when its tests end. Flags the daemon does not model
|
|
176
|
+
(`--locked`, `--offline`, …) and arguments after `--` disqualify a pair
|
|
177
|
+
only when they differ between the two requests. `check` never rides
|
|
178
|
+
`clippy`: a lint failure would misreport the check.
|
|
153
179
|
3. **Batch folding:** compatible queued compile or test requests are combined
|
|
154
180
|
into one invocation.
|
|
155
181
|
|
|
182
|
+
A request that could not attach is logged at debug level with the gate that
|
|
183
|
+
refused it (`subcommand`, `opaque-arguments`, `passthrough`,
|
|
184
|
+
`compile-surface`, `packages`, `targets`, `channels`,
|
|
185
|
+
`leader-build-finished`) and both tickets, and `hauler status --json`
|
|
186
|
+
counts refusals per gate under `metrics.attach_rejections` (the nearest
|
|
187
|
+
miss when several leaders were considered).
|
|
188
|
+
|
|
156
189
|
Each admitted leader starts one Cargo process. Identity, coverage, and folded
|
|
157
190
|
batch requests share that process and receive its streamed output. A failed
|
|
158
191
|
stronger compile does not satisfy a coverage or compile-batch attachment; the
|
|
159
192
|
attached request returns to its lane unless its required compilation units were
|
|
160
|
-
already observed as successful.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
`
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
193
|
+
already observed as successful. Compile batches (`build`, `check`, `clippy`)
|
|
194
|
+
require the same target selection and features, and the same arguments after
|
|
195
|
+
`--`: `cargo clippy -p a -- -D warnings` and `cargo clippy -p b -- -D warnings`
|
|
196
|
+
become `cargo clippy -p a -p b -- -D warnings`, with the trailer once. Under
|
|
197
|
+
`-D warnings` another participant's warnings fail the composite; a participant
|
|
198
|
+
whose own units compiled cleanly is still released as done, the rest rerun
|
|
199
|
+
alone. Folded tests share the composite process and output. `cargo test`
|
|
200
|
+
requests fold when their `--test` / `--lib` selection and harness flags match
|
|
201
|
+
— `--test-threads=N`, `--nocapture`, and `--quiet` are the only flags a
|
|
202
|
+
composite carries, and only when every participant asked for the same set.
|
|
203
|
+
Requests naming different packages may also differ in bare name filters:
|
|
204
|
+
`cargo test -p a -- f1` and `cargo test -p b -- f2` become `cargo test -p a -p
|
|
205
|
+
b --no-fail-fast -- f1 f2`, a run over the union of packages with the union of
|
|
206
|
+
filters (a filter for one package may also match test names in another).
|
|
207
|
+
Requests naming the same packages share no compile and still need the same
|
|
208
|
+
filters; unfiltered runs fold only with unfiltered runs; `--exact`, `--skip`,
|
|
209
|
+
`--ignored`, `--include-ignored`, `--list`, `--format`, `--logfile`, or any
|
|
210
|
+
other harness flag keeps a run out of composites. `cargo nextest run`
|
|
211
|
+
requests fold only on an identical filterset. On success every participant
|
|
212
|
+
shares the exit. When the composite
|
|
213
|
+
fails, a participant inherits that failure only if it named every package and
|
|
214
|
+
every filter the composite ran; otherwise the failing tests may belong to
|
|
215
|
+
another participant's package or filter, so it is requeued and runs alone
|
|
169
216
|
(cargo's test output does not attribute failures to packages). The leader
|
|
170
217
|
keeps the composite exit, as compile-batch leaders do.
|
|
171
218
|
|
|
@@ -179,7 +226,9 @@ stdout.
|
|
|
179
226
|
|
|
180
227
|

|
|
181
228
|
|
|
182
|
-
The scheduler estimates run cost from per-intent EWMA history
|
|
229
|
+
The scheduler estimates run cost from per-intent EWMA history, split into a
|
|
230
|
+
compile phase and an execution phase once the ledger has seen
|
|
231
|
+
`buildFinishedAtMs` on that intent. It can also use
|
|
183
232
|
per-crate timing data from kache. Lower-cost work, requests with more attached
|
|
184
233
|
callers, dependency-unblocking work, and recently edited packages receive a
|
|
185
234
|
lower scheduling score. Waiting time lowers the score further so broad work
|
|
@@ -234,14 +283,14 @@ own `-j` flag or `CARGO_BUILD_JOBS` always wins over both.
|
|
|
234
283
|
|
|
235
284
|
| Capability | Behavior |
|
|
236
285
|
| --- | --- |
|
|
237
|
-
| Work sharing | Identical requests attach, covered checks attach, and compatible queued compile or test requests fold. |
|
|
286
|
+
| Work sharing | Identical requests attach, covered checks and compile-only `test --no-run` requests attach, and compatible queued compile or test requests fold. |
|
|
238
287
|
| Lane isolation | A workspace-root and target-directory pair is serialized independently from other lanes. |
|
|
239
288
|
| 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. |
|
|
240
289
|
| 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. |
|
|
241
|
-
| Scheduling | EWMA estimates, optional kache priors, fan-out, dependency topology, recent edits, and request age determine lane order; `--after cc-N` holds a request until the named tickets settle. |
|
|
290
|
+
| Scheduling | Per-phase EWMA estimates (compile vs execute), optional kache priors, fan-out, dependency topology, recent edits, and request age determine lane order; `--after cc-N` holds a request until the named tickets settle. |
|
|
242
291
|
| Persistence | Tickets, output tails, timings, outcomes, and savings are stored in SQLite; every leader run's whole combined output is kept on disk as `<state dir>/tickets/<ticket>.log`. |
|
|
243
292
|
| 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. |
|
|
244
|
-
| Wait escalation | A queued request waiting longer than the larger of twice its own estimate and ten minutes is flagged as delayed; running jobs silent for more than five minutes show a quiet-duration hint. A running job past three times its estimate whose process tree has burned no CPU and printed nothing for ten minutes is flagged `stalled`; only a stalled job whose submitting connection is gone is killed automatically. |
|
|
293
|
+
| Wait escalation | A queued request waiting longer than the larger of twice its own estimate and ten minutes is flagged as delayed; running jobs silent for more than five minutes show a quiet-duration hint. A live head past three times its estimate that is still burning CPU or printing is flagged `estimateState: overrun` (its followers see `queue.headEstimateState`) and contributes its history p90 remaining — never less than one more estimate's worth — to the queue ETA instead of zero. A running job past three times its estimate whose process tree has burned no CPU and printed nothing for ten minutes is flagged `stalled`; only a stalled job whose submitting connection is gone is killed automatically. |
|
|
245
294
|
| Daemon status | `running`, `stopped`, or `unresponsive`: a socket that exists but does not answer within its budget is reported as unresponsive, never as stopped. |
|
|
246
295
|
|
|
247
296
|
### Tickets and long-running requests
|
|
@@ -294,7 +343,12 @@ macOS; other platforms do not detect stalls). A ticket is flagged `stalled`
|
|
|
294
343
|
when its elapsed time exceeds `CARGO_HAULER_STALL_ESTIMATE_FACTOR` (3) times
|
|
295
344
|
its estimate, the tree's CPU time has not changed for
|
|
296
345
|
`CARGO_HAULER_STALL_IDLE_MS` (ten minutes), and it printed nothing in that
|
|
297
|
-
window.
|
|
346
|
+
window. A head that has crossed that estimate multiple but is still burning
|
|
347
|
+
CPU or printing is flagged `estimateState: overrun` instead (with the
|
|
348
|
+
intent's `p90Ms`, and `queue.headEstimateState: overrun` on the tickets
|
|
349
|
+
behind it): the queue ETA uses the intent's p90 remaining — never less than
|
|
350
|
+
one more estimate's worth — rather than zero, and agents can background it
|
|
351
|
+
without treating it as deadlocked. `hauler status`, `hauler_status`, and the dashboard show `stalled`
|
|
298
352
|
with the idle duration; `hauler result` and `hauler_result` answer `ticket
|
|
299
353
|
looks stalled (no CPU for Nm) — hauler kill cc-N`; `hauler await` heartbeats
|
|
300
354
|
say the same. Riders of a stalled leader report the leader's stall and its
|
|
@@ -325,7 +379,7 @@ send (`outputPath`, `after`), so a plain version difference alone does not
|
|
|
325
379
|
break `status`, `result`, or `await`; finish or `hauler kill` what is in
|
|
326
380
|
flight before restarting if the work matters.
|
|
327
381
|
|
|
328
|
-
The `tool/after`
|
|
382
|
+
The `tool/after` hook checks the session's background tickets — `--bg`,
|
|
329
383
|
`hauler_request`, and synchronous requests the client converted to a ticket
|
|
330
384
|
— and, on the first tool call after one finishes, adds its result to the
|
|
331
385
|
agent context. A foreground ticket streamed its exit to the shell the agent
|
|
@@ -392,6 +446,17 @@ reports the slowest crates by profile (`<KacheStats>`). Without that index,
|
|
|
392
446
|
estimates come from the daemon's EWMA history. A missing or incompatible index
|
|
393
447
|
is reported as unavailable and never rejects a request.
|
|
394
448
|
|
|
449
|
+
The same panel surfaces store pressure: blob bytes recorded in the index
|
|
450
|
+
against kache's `local_max_size` (from `KACHE_MAX_SIZE` or
|
|
451
|
+
`$XDG_CONFIG_HOME/kache/config.toml`; when neither applies the panel says
|
|
452
|
+
"limit unknown" and why), the last GC from `gc_stats.json` beside the index
|
|
453
|
+
— when it ran, how long it took, what it evicted, and any `gc: skipping
|
|
454
|
+
eviction` warnings from kache's `auto-gc.log`/`daemon.log` during that run —
|
|
455
|
+
and `key_ms` mean/p95 over the tail of the events sidecar. Warnings appear
|
|
456
|
+
when the store is over its limit or the last GC declined or skipped evictions;
|
|
457
|
+
a missing or unparsable file renders as unavailable with its reason, never as
|
|
458
|
+
an empty store.
|
|
459
|
+
|
|
395
460
|

|
|
396
461
|
|
|
397
462
|
## Install
|
|
@@ -537,9 +602,14 @@ unset, the daemon reads kache's configured local store from
|
|
|
537
602
|
document names the one in use (`state dir …` in the header; `stateRoot` in
|
|
538
603
|
`--json`), so a `CARGO_HAULER_STATE_DIR` change is visible on the next
|
|
539
604
|
command rather than discovered from an empty ledger.
|
|
540
|
-
- Test
|
|
541
|
-
|
|
542
|
-
|
|
605
|
+
- Test execution is never shared by coverage: a `test`, `nextest`, or `bench`
|
|
606
|
+
that runs tests attaches only by identity or batch folding. Only a
|
|
607
|
+
compile-only `test --no-run` / `bench --no-run` rides a running `test` /
|
|
608
|
+
`bench`, and it is released by the leader's build alone. Folded `test` and
|
|
609
|
+
`nextest` requests receive the composite output, and a composite may run
|
|
610
|
+
more than one participant asked for (another package, another name
|
|
611
|
+
filter); its failure is inherited only by participants that asked for
|
|
612
|
+
everything it ran, the rest rerun alone.
|
|
543
613
|
- The `cargo clean` guard probes the daemon for 250 ms. Active work denies
|
|
544
614
|
the clean; an idle daemon brokers it; a daemon that accepts but does not
|
|
545
615
|
answer in time is busy, so the clean is brokered and the lane serializes
|
|
@@ -558,9 +628,9 @@ unset, the daemon reads kache's configured local store from
|
|
|
558
628
|
<summary><strong>How the app is built</strong> — agent-bundle application structure, testing, and development (click to expand)</summary>
|
|
559
629
|
|
|
560
630
|
The plugin is an [agent-bundle](https://github.com/ScriptedAlchemy/agent-bundle)
|
|
561
|
-
application: six MCP tools, a routed CLI,
|
|
562
|
-
browser dashboard, all rendered from one
|
|
563
|
-
layout. This section is for contributors; using cargo-hauler needs none of it.
|
|
631
|
+
application: six MCP tools, a routed CLI, two hook routes plus two declared
|
|
632
|
+
shell hooks, two skills, and a browser dashboard, all rendered from one
|
|
633
|
+
component library through one shared layout. This section is for contributors; using cargo-hauler needs none of it.
|
|
564
634
|
|
|
565
635
|
### Application structure
|
|
566
636
|
|
|
@@ -577,7 +647,8 @@ src/
|
|
|
577
647
|
mcp/hauler/tools/*.tsx hauler_status, _log, _last, _await, _result, _request
|
|
578
648
|
mcp/hauler/apps/dashboard.tsx the MCP App (ui://cargo-hauler/dashboard.html)
|
|
579
649
|
cli/*.tsx, cli/daemon.ts the routed `cargo-hauler` CLI, same components
|
|
580
|
-
events/{session/start,
|
|
650
|
+
events/{session/start,stop}.tsx rendered hook routes
|
|
651
|
+
hooks/fast-path/ the declared tool/before and tool/after shell hooks
|
|
581
652
|
skills/cargo-hauler/SKILL.md, skills/hauler-dashboard/SKILL.tsx
|
|
582
653
|
scripts/hauler.ts the `hauler` process entry hooks rewrite cargo to
|
|
583
654
|
daemon/, client/, hooks/, shim/, lib/ the broker and its libraries
|
|
@@ -608,6 +679,24 @@ through one layout, the way a page framework's `layout.tsx` wraps every page:
|
|
|
608
679
|
|
|
609
680
|
Event routes are host protocol responses and are never wrapped.
|
|
610
681
|
|
|
682
|
+
#### The shell hooks (`src/hooks/fast-path/`)
|
|
683
|
+
|
|
684
|
+
`tool/before` and `tool/after` are not rendered routes. They are declared in
|
|
685
|
+
`agent-bundle.config.ts` under `hooks` as handler modules
|
|
686
|
+
(`shell-before.ts`, `shell-after.ts`), which the framework compiles into
|
|
687
|
+
standalone entries — `hooks/before-tool-shell-before-<hash>.mjs`,
|
|
688
|
+
`hooks/after-tool-shell-after-<hash>.mjs` — that carry no React, no Flight
|
|
689
|
+
worker, and no Effect. Each entry decides on the raw command first
|
|
690
|
+
(`tokens.ts`; `session-ping.ts` for the completion ping) and reaches the
|
|
691
|
+
rewrite (`before-shell.ts`) or the telemetry and notification code
|
|
692
|
+
(`after-shell.ts`) through a deliberate dynamic `import()`, the one place in
|
|
693
|
+
the codebase that imports lazily. The handler contract has no `allow`
|
|
694
|
+
outcome, so a fully brokered rewrite writes the host's own allow shape
|
|
695
|
+
(`allow-output.ts`) instead of `continue` + `updatedInput`, which would make
|
|
696
|
+
the host prompt for the rewrite. Everything else — `continue`, `deny` with a
|
|
697
|
+
reason, `additionalContext` — goes through the generated wrapper's
|
|
698
|
+
projection.
|
|
699
|
+
|
|
611
700
|
#### The daemon provider (`src/providers/hauler-daemon.ts`)
|
|
612
701
|
|
|
613
702
|
One request-context provider mounts `providers.haulerDaemon` for every tool,
|
|
@@ -691,9 +780,9 @@ the same filter as its `session` field). Results carry
|
|
|
691
780
|
| `tool:hauler/hauler_request` · `cli:request` | submit a background request | `RequestDocument` |
|
|
692
781
|
| `cli:daemon` | `run` / `start` / `stop` / `status` | plain JSON, exit code from the result |
|
|
693
782
|
| `event:session/start` | new session | daemon state and the no-kill rule as context |
|
|
694
|
-
| `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 |
|
|
695
|
-
| `event:tool/after` | shell tool finished | injects finished background-ticket results once per session |
|
|
696
783
|
| `event:stop` | agent stopping | holds the stop while a foreground ticket is pending (bounded, re-deniable) |
|
|
784
|
+
| `hooks.beforeTool` (`src/hooks/fast-path/shell-before.ts`) | shell tool about to run | `continue` without loading anything for a non-cargo command; otherwise 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 |
|
|
785
|
+
| `hooks.afterTool` (`src/hooks/fast-path/shell-after.ts`) | shell tool finished | one bounded completion ping per call; injects finished background-ticket results once per session |
|
|
697
786
|
|
|
698
787
|
#### Skills
|
|
699
788
|
|
|
@@ -711,6 +800,18 @@ render MCP Apps. It shows contention and admission, in-flight and queued
|
|
|
711
800
|
work, metrics windows, optional kache data, lanes, and history, with a live
|
|
712
801
|
output drawer per ticket.
|
|
713
802
|
|
|
803
|
+
Each metrics window also reports queue wait against run time for leaders,
|
|
804
|
+
with the wait split by cause: *lane-bound* (a same-lane leader was still
|
|
805
|
+
compiling — before its `Finished` line or exit), *permit-bound* (every
|
|
806
|
+
admission permit was held and no same-lane compile was to blame), and *other*
|
|
807
|
+
(admission holds, `--after` prerequisites, scheduling latency). The
|
|
808
|
+
classification is a pure sweep over ledger rows (`src/daemon/wait-split.ts`)
|
|
809
|
+
run once per status refresh against the daemon's current permit count, which
|
|
810
|
+
the tile states; runs admitted under an earlier cap are classified against
|
|
811
|
+
today's. With `buildFinishedAtMs` on the row, the by-command split adds
|
|
812
|
+
compile vs execution time for test/run/bench leaders and the window reports
|
|
813
|
+
the lane time the execution-phase hand-back released.
|
|
814
|
+
|
|
714
815
|
### Testing
|
|
715
816
|
|
|
716
817
|
```sh
|
|
@@ -722,7 +823,7 @@ artifact build, at the harness proof levels:
|
|
|
722
823
|
|
|
723
824
|
| Level | Suite | What it proves |
|
|
724
825
|
| --- | --- | --- |
|
|
725
|
-
| route-unit | `routes`, `layout`, `streaming`, `events` | documents, shell metadata, Suspense fallbacks and settled values, lineage attribution, event decisions |
|
|
826
|
+
| route-unit | `routes`, `layout`, `streaming`, `events` | documents, shell metadata, Suspense fallbacks and settled values, lineage attribution, event decisions (the shell hooks are unit-tested in `tests/hook-fast-path.test.ts` and against their compiled entries in `tests/hooks-simulate.test.ts`) |
|
|
726
827
|
| cli-dispatch | `cli-dispatch`, `layout` | argv through the routed CLI shell; Markdown wrapped by the shell, `--json` bare |
|
|
727
828
|
| script-dispatch | `script-dispatch` | the `hauler` entry through its `main` envelope as its own process |
|
|
728
829
|
| mcp-in-memory | `mcp-surface`, `layout` | tool names, `outputSchema`, the dashboard resource link, `_meta.hauler`, and a live fixture broker over the in-memory transport |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"hooks":[{"event":"
|
|
1
|
+
{"hooks":[{"event":"afterTool","id":"hook:after-tool:shell-after:264de610","name":"after-tool-shell-after-264de610","path":"claude/hooks/after-tool-shell-after-264de610.mjs","target":"claude","timeout":10},{"event":"beforeTool","id":"hook:before-tool:shell-before:9212219e","name":"before-tool-shell-before-9212219e","path":"claude/hooks/before-tool-shell-before-9212219e.mjs","target":"claude","timeout":10},{"event":"sessionStart","id":"hook:event-route:session-start","name":"event-route-session-start","path":"claude/hooks/event-route-session-start.mjs","target":"claude","timeout":5},{"event":"stop","id":"hook:event-route:stop","name":"event-route-stop","path":"claude/hooks/event-route-stop.mjs","target":"claude","timeout":900},{"event":"afterTool","id":"hook:after-tool:shell-after:264de610","name":"after-tool-shell-after-264de610","path":"codex/hooks/after-tool-shell-after-264de610.mjs","target":"codex","timeout":10},{"event":"beforeTool","id":"hook:before-tool:shell-before:9212219e","name":"before-tool-shell-before-9212219e","path":"codex/hooks/before-tool-shell-before-9212219e.mjs","target":"codex","timeout":10},{"event":"sessionStart","id":"hook:event-route:session-start","name":"event-route-session-start","path":"codex/hooks/event-route-session-start.mjs","target":"codex","timeout":5},{"event":"stop","id":"hook:event-route:stop","name":"event-route-stop","path":"codex/hooks/event-route-stop.mjs","target":"codex","timeout":900},{"event":"afterTool","id":"hook:after-tool:shell-after:264de610","name":"after-tool-shell-after-264de610","path":"cursor/hooks/after-tool-shell-after-264de610.mjs","target":"cursor","timeout":10},{"event":"beforeTool","id":"hook:before-tool:shell-before:9212219e","name":"before-tool-shell-before-9212219e","path":"cursor/hooks/before-tool-shell-before-9212219e.mjs","target":"cursor","timeout":10},{"event":"sessionStart","id":"hook:event-route:session-start","name":"event-route-session-start","path":"cursor/hooks/event-route-session-start.mjs","target":"cursor","timeout":5},{"event":"stop","id":"hook:event-route:stop","name":"event-route-stop","path":"cursor/hooks/event-route-stop.mjs","target":"cursor","timeout":900}]}
|