cargo-hauler 0.4.6 → 0.4.8
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 +12 -0
- package/README.md +325 -224
- 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 +165 -477
- package/artifact/claude/bin/cargo-hauler.mjs +519 -528
- 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/event-route-tool-after.mjs +2 -2
- package/artifact/claude/hooks/event-route-tool-before.mjs +2 -2
- package/artifact/claude/hooks/hooks-flight.mjs +146 -36
- package/artifact/claude/mcp/mcp-hauler-2b8242f9-flight.mjs +504 -491
- package/artifact/claude/mcp/mcp-hauler-2b8242f9.mjs +171 -479
- package/artifact/claude/mcp-apps/dashboard.html +1 -1
- package/artifact/claude/scripts/hauler.mjs +156 -163
- package/artifact/codex/.codex-plugin/plugin.json +1 -1
- package/artifact/codex/INSTALL.md +1 -1
- package/artifact/codex/bin/cargo-hauler-flight.mjs +165 -477
- package/artifact/codex/bin/cargo-hauler.mjs +519 -528
- 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/event-route-tool-after.mjs +2 -2
- package/artifact/codex/hooks/event-route-tool-before.mjs +2 -2
- package/artifact/codex/hooks/hooks-flight.mjs +146 -36
- package/artifact/codex/mcp/mcp-hauler-2b8242f9-flight.mjs +504 -491
- package/artifact/codex/mcp/mcp-hauler-2b8242f9.mjs +171 -479
- package/artifact/codex/mcp-apps/dashboard.html +1 -1
- package/artifact/codex/scripts/hauler.mjs +156 -163
- package/artifact/cursor/.cursor-plugin/plugin.json +1 -1
- package/artifact/cursor/INSTALL.md +1 -1
- package/artifact/cursor/bin/cargo-hauler-flight.mjs +165 -477
- package/artifact/cursor/bin/cargo-hauler.mjs +519 -528
- 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/event-route-tool-after.mjs +2 -2
- package/artifact/cursor/hooks/event-route-tool-before.mjs +2 -2
- package/artifact/cursor/hooks/hooks-flight.mjs +146 -36
- package/artifact/cursor/install.mjs +1 -1
- package/artifact/cursor/mcp/mcp-hauler-2b8242f9-flight.mjs +504 -491
- package/artifact/cursor/mcp/mcp-hauler-2b8242f9.mjs +171 -479
- package/artifact/cursor/mcp-apps/dashboard.html +1 -1
- package/artifact/cursor/scripts/hauler.mjs +156 -163
- package/artifact/portable/INSTALL.md +1 -1
- package/artifact/portable/bin/cargo-hauler-flight.mjs +165 -477
- package/artifact/portable/bin/cargo-hauler.mjs +519 -528
- package/artifact/portable/install.mjs +1 -1
- package/artifact/portable/mcp/mcp-hauler-2b8242f9-flight.mjs +161 -475
- package/artifact/portable/mcp/mcp-hauler-2b8242f9.mjs +164 -472
- package/artifact/portable/mcp-apps/dashboard.html +1 -1
- package/artifact/portable/plugin.json +1 -1
- package/artifact/portable/scripts/hauler.mjs +156 -163
- package/dist/bin/cargo-hauler-flight.mjs +165 -477
- package/dist/bin/cargo-hauler.js +519 -528
- package/dist/bin/hauler.js +156 -163
- package/package.json +12 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 3ad6b7e: Declare no runtime `dependencies`: `npm install -g cargo-hauler` fetches one tarball and nothing else. 0.4.7 listed the build-time stack (`@agent-bundle/runtime` as a pkg.pr.new tarball, `bashjsast` as a `github:` ref, `effect`, `react`, `zod`, …) under `dependencies` even though every shipped file bundles what it uses, so npm 12's default `allow-remote=none` / `allow-git=none` refused the install (`EALLOWREMOTE` for the tarball, `EALLOWGIT` for the git ref). The whole stack now lives under `devDependencies`; the emitted packs and `dist/bin` executables are unchanged. (#82)
|
|
8
|
+
|
|
9
|
+
## 0.4.7
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- f13360f: Internal simplification sweep: one socket-errno walker (`src/lib/socket-errors.ts`) serves the client, the health probe, and the hooks; `defaultCargoProfile`, the ms clamps, and `diagnosticCounts` are shared instead of copied; request statuses and attach modes are one `as const` list that feeds the types, the zod schemas, and the ledger's SQL filters; the ack is the broker's `SubmitResult` spread once; the before-shell hook skips the bash parse for commands that cannot name cargo, and the after-shell hook no longer rewrites its state file when no ticket finished. The `hauler await` heartbeat spells whole minutes as `2m` rather than `2m0s`.
|
|
14
|
+
|
|
3
15
|
## 0.4.6
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -2,221 +2,74 @@
|
|
|
2
2
|
|
|
3
3
|
# cargo-hauler
|
|
4
4
|
|
|
5
|
-
Cargo
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
**One Cargo, many agents.** cargo-hauler is a broker for `cargo` on a machine
|
|
6
|
+
where several AI coding sessions (Claude Code, Codex, Cursor), scripts, and
|
|
7
|
+
terminals build the same Rust workspaces at once. It stops them from fighting
|
|
8
|
+
over the build directory, runs the work once, and hands every requester its
|
|
9
|
+
own result.
|
|
10
|
+
|
|
11
|
+
## The problem
|
|
12
|
+
|
|
13
|
+
Agents run `cargo check`, `cargo test`, and `cargo build` constantly and
|
|
14
|
+
independently. On a shared machine that means the same crates compiling five
|
|
15
|
+
times over, everyone blocking on `Blocking waiting for file lock on build
|
|
16
|
+
directory`, a saturated CPU, and shell tools that kill a build at their ten
|
|
17
|
+
minute timeout with nothing to show for it. Nobody can see what is running,
|
|
18
|
+
what is queued, or why their command is slow.
|
|
19
|
+
|
|
20
|
+
## What it does
|
|
21
|
+
|
|
22
|
+
- **Intercepts** every Cargo invocation: hooks rewrite `cargo …` in agent
|
|
23
|
+
shells, an optional PATH shim covers scripts and terminals, and MCP tools
|
|
24
|
+
let agents submit work directly.
|
|
25
|
+
- **Coalesces** compatible requests: a byte-identical command attaches to the
|
|
26
|
+
run already in flight, a narrower `check` rides a broader `build`, and
|
|
27
|
+
queued tests with the same selection fold into one `--no-fail-fast` run.
|
|
28
|
+
Riders receive the leader's streamed output and exit code as if they had
|
|
29
|
+
run it themselves.
|
|
30
|
+
- **Schedules and admits** work per workspace lane under a machine-wide
|
|
31
|
+
permit cap, with load, memory-pressure, and heavy-profile gates, cost
|
|
32
|
+
estimates from run history and [kache](https://github.com/ScriptedAlchemy/kache)
|
|
33
|
+
priors, and `--after cc-N` dependencies when a test needs a build first.
|
|
34
|
+
- **Keeps a ticket** (`cc-N`) for every request: status, exit code, live
|
|
35
|
+
output tail, the whole output log on disk, timings, and attribution to the
|
|
36
|
+
session that asked. Long builds become background tickets that agents
|
|
37
|
+
`await`, and a stop hook holds an agent's turn until its build lands.
|
|
38
|
+
- **Protects the machine**: `cargo clean` is refused while builds are in
|
|
39
|
+
flight, stalled processes are flagged (and killed when their owner is
|
|
40
|
+
gone), and `hauler kill` frees a wedged lane through the broker instead of
|
|
41
|
+
by PID.
|
|
42
|
+
- **Shows everything** in a live dashboard: contention, in-flight and queued
|
|
43
|
+
work, lanes, per-command timings, compute avoided, and kache data.
|
|
15
44
|
|
|
16
45
|

|
|
17
46
|
|
|
18
|
-
##
|
|
19
|
-
|
|
20
|
-
Everything an agent sees is a React Server Component rendered by the
|
|
21
|
-
agent-bundle runtime into an Agent Document, then lowered to MCP content, CLI
|
|
22
|
-
Markdown, `--json`, or a host hook envelope. There is no hand-written server,
|
|
23
|
-
argv parser, or string-concatenated Markdown; the `src/` tree is the app.
|
|
24
|
-
|
|
25
|
-
```text
|
|
26
|
-
src/
|
|
27
|
-
layout.tsx the hauler shell around every rendered route
|
|
28
|
-
providers/hauler-daemon.ts request-scoped daemon connection + health probe
|
|
29
|
-
components/ typed components over pure view-models
|
|
30
|
-
mcp/hauler/tools/*.tsx hauler_status, _log, _last, _await, _result, _request
|
|
31
|
-
mcp/hauler/apps/dashboard.tsx the MCP App (ui://cargo-hauler/dashboard.html)
|
|
32
|
-
cli/*.tsx, cli/daemon.ts the routed `cargo-hauler` CLI, same components
|
|
33
|
-
events/{session/start,tool/before,tool/after,stop}.tsx hook routes
|
|
34
|
-
skills/cargo-hauler/SKILL.md, skills/hauler-dashboard/SKILL.tsx
|
|
35
|
-
scripts/hauler.ts the `hauler` process entry hooks rewrite cargo to
|
|
36
|
-
daemon/, client/, hooks/, shim/, lib/ the broker and its libraries
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### The shell (`src/layout.tsx`)
|
|
40
|
-
|
|
41
|
-
Every rendered route — MCP tool, CLI command, rendered script — composes
|
|
42
|
-
through one layout, the way a page framework's `layout.tsx` wraps every page:
|
|
43
|
-
|
|
44
|
-
- **Header:** `<DaemonBadge>` prints what the request-start probe proved and
|
|
45
|
-
which state directory it is: `cargo-hauler · daemon running (pid 4021) ·
|
|
46
|
-
2/5 permits +1 riding, 1 queued · 2 lanes busy · up since 3h ago · state dir
|
|
47
|
-
/fast/cache/cargo-hauler`, or `daemon stopped · no socket; it starts on
|
|
48
|
-
demand…`, or `daemon unresponsive · did not accept a connection within
|
|
49
|
-
750ms (machine saturated)…`. When the daemon is another build than the CLI
|
|
50
|
-
or MCP server rendering the document, a second line says so: `cargo-hauler
|
|
51
|
-
· daemon 0.4.2 ≠ cli 0.4.4 — restart it with \`hauler daemon restart\``.
|
|
52
|
-
- **Body:** the route's own document, unchanged. The route keeps its
|
|
53
|
-
`<Agent.Result value>`; the runtime merges it into the shell so
|
|
54
|
-
`structuredContent` and `--json` are exactly what the route declared.
|
|
55
|
-
- **Footer:** `<LineageFooter>` names the conversation the request belongs to
|
|
56
|
-
(`Requested by conversation conv-7f (depth 1 under conv-2a; registry)`),
|
|
57
|
-
read synchronously with `useAgent()`, and stays silent when the host cannot
|
|
58
|
-
place the request rather than guessing.
|
|
59
|
-
- **`_meta.hauler`** on every MCP result: `route`, `surface`, `server`,
|
|
60
|
-
`version`, `daemon: { state, pid? }`, `lineage: { conversation, root, depth } | null`.
|
|
61
|
-
|
|
62
|
-
Event routes are host protocol responses and are never wrapped.
|
|
63
|
-
|
|
64
|
-
### The daemon provider (`src/providers/hauler-daemon.ts`)
|
|
65
|
-
|
|
66
|
-
One request-context provider mounts `providers.haulerDaemon` for every tool,
|
|
67
|
-
command, event, and script: the resolved `config` (state dir, socket, ledger)
|
|
68
|
-
and a `health` value from one bounded `status` probe:
|
|
69
|
-
|
|
70
|
-
| `health.state` | meaning |
|
|
71
|
-
| --- | --- |
|
|
72
|
-
| `running` | `pid`, `startedAtMs`, `latencyMs`, `running` (permit holders), `riding` (attached), `queued`, `busyLanes`, `maxConcurrent`, and `version` when the daemon states one (on the status report from 0.4.5, by one extra `ping` for older daemons) |
|
|
73
|
-
| `stopped` | `socket-missing` (starts on demand) or `connection-refused` (stale socket) |
|
|
74
|
-
| `unresponsive` | `accept-timeout` (never accepted), `answer-timeout` (accepted, no `status-result`), or `connection-closed` within the probe budget (750 ms for the accept and for the answer); ledger reads still work |
|
|
75
|
-
| `unreachable` | `open-failed` with the errno (`EACCES`, `EMFILE`, …): the socket is present but could not be opened, which is not evidence the daemon is down |
|
|
76
|
-
| `unprobed` | `event-surface`: hooks run on every shell command and skip the probe by design |
|
|
77
|
-
|
|
78
|
-
The provider fails closed on nothing it can observe and fabricates nothing.
|
|
79
|
-
Routes read it through `requestDaemon(context)` / `requestDaemonConfig(context)`;
|
|
80
|
-
tests inject a fixture through the harness `context.providers` seam.
|
|
81
|
-
|
|
82
|
-
### Components (`src/components/`)
|
|
83
|
-
|
|
84
|
-
Components render view-models and nothing else. The models are pure functions
|
|
85
|
-
in `view-models.ts`, so the MCP document, the CLI Markdown, and a test
|
|
86
|
-
assertion share one derivation.
|
|
87
|
-
|
|
88
|
-
| Component | Renders |
|
|
89
|
-
| --- | --- |
|
|
90
|
-
| `<TicketCard>` | one ticket: headline, attribution, lane, queue position, attach mode, timings, exit, then `<BuildDiagnostics>` and `<LogTail>` |
|
|
91
|
-
| `<TicketList>` | the in-flight and recent tables of status, and the whole of log |
|
|
92
|
-
| `<LaneBoard>` | busy lanes with their leader ticket, its command, and how long it has run |
|
|
93
|
-
| `<AdmissionState>` | permits in use, load, memory clamp, sharing savings; calls out a paused admission gate |
|
|
94
|
-
| `<KacheStats>` | kache coverage and freshness, slowest crates by profile, or an honest "not detected" |
|
|
95
|
-
| `<LogTail>` | the captured output tail, labelled live while the run is in progress |
|
|
96
|
-
| `<FullOutput>` | where the ticket's whole output log lives and how large it is; under `full`, the log itself in code-block chunks |
|
|
97
|
-
| `<BuildDiagnostics>` | an index of cargo `error[E…]`/`warning:` blocks (level / code / message / location) followed by every captured block verbatim |
|
|
98
|
-
| `<DashboardLink>` | where the MCP App lives and how to open it elsewhere |
|
|
99
|
-
| `<TicketGuidance>` | what to do next, one component per ticket status |
|
|
100
|
-
| `<DaemonBadge>`, `<LineageFooter>` | the shell header and footer |
|
|
101
|
-
| `<EmptyState>`, `<UnavailableState>`, `<ErrorState>` | the three non-happy shapes every document may take |
|
|
102
|
-
|
|
103
|
-
`documents.tsx` composes them into one document per hauler result
|
|
104
|
-
(`StatusDocument`, `LogDocument`, `LastDocument`, `ResultDocument`,
|
|
105
|
-
`AwaitDocument`, `RequestDocument`); the MCP tool and the CLI command for the
|
|
106
|
-
same operation render the same document with different command spellings
|
|
107
|
-
(`surface.ts`).
|
|
108
|
-
|
|
109
|
-
### Streaming (`src/components/streaming.tsx`)
|
|
110
|
-
|
|
111
|
-
`hauler_await` and `hauler_log` are progressive documents. Each is a
|
|
112
|
-
valueless `Agent.Result` container around one `Suspense` boundary:
|
|
113
|
-
|
|
114
|
-
- `<AwaitStream>`: the fallback is the ticket **as it is now** — its live
|
|
115
|
-
output tail and a progress node — rendered before the daemon-side wait
|
|
116
|
-
blocks; the settled child is the ordinary `AwaitDocument`. MCP hosts receive
|
|
117
|
-
the fallback's progress as notifications and the settled value as
|
|
118
|
-
`structuredContent`; the routed CLI updates the terminal in place. Heartbeats
|
|
119
|
-
(queue position, elapsed time, cost estimate) still flow through
|
|
120
|
-
`context.progress`.
|
|
121
|
-
- `<LogStream>`: a "reading the ledger" progress frame, then the listing.
|
|
122
|
-
|
|
123
|
-
### Attribution and lineage
|
|
124
|
-
|
|
125
|
-
`hauler_request` attributes tickets from the request context: an explicit
|
|
126
|
-
`host`/`session` wins; otherwise the negotiated host and native session are
|
|
127
|
-
used; and when the transport publishes no session id (bare stdio MCP), the
|
|
128
|
-
conversation from `request.lineage` becomes the session of record. That is
|
|
129
|
-
what makes parallel agents' builds attributable in the ledger, the dashboard,
|
|
130
|
-
and `hauler status --session <conversation>` (the `hauler_status` tool takes
|
|
131
|
-
the same filter as its `session` field). Results carry
|
|
132
|
-
`attribution: { host, session, lineage }`.
|
|
133
|
-
|
|
134
|
-
### Routes
|
|
135
|
-
|
|
136
|
-
| Route | Surface | Document |
|
|
137
|
-
| --- | --- | --- |
|
|
138
|
-
| `tool:hauler/hauler_status` · `cli:status` | queue, lanes, admission, kache, filters | `StatusDocument`; the tool advertises the dashboard App |
|
|
139
|
-
| `tool:hauler/hauler_log` · `cli:log` | recent requests | `LogStream` → `LogDocument` |
|
|
140
|
-
| `tool:hauler/hauler_last` · `cli:last` | most recent request | `LastDocument` |
|
|
141
|
-
| `tool:hauler/hauler_await` · `cli:await` | long-poll a ticket (≤ 2 h) | `AwaitStream` → `AwaitDocument` |
|
|
142
|
-
| `tool:hauler/hauler_result` · `cli:result` | one ticket, live tail while running; `full` renders the whole on-disk output log | `ResultDocument` (`<FullOutput>`) |
|
|
143
|
-
| `tool:hauler/hauler_kill` · `cli:kill` | stop a queued or running ticket | `KillDocument` |
|
|
144
|
-
| `tool:hauler/hauler_request` · `cli:request` | submit a background request | `RequestDocument` |
|
|
145
|
-
| `cli:daemon` | `run` / `start` / `stop` / `status` | plain JSON, exit code from the result |
|
|
146
|
-
| `event:session/start` | new session | daemon state and the no-kill rule as context |
|
|
147
|
-
| `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 |
|
|
148
|
-
| `event:tool/after` | shell tool finished | injects finished background-ticket results once per session |
|
|
149
|
-
| `event:stop` | agent stopping | holds the stop while a foreground ticket is pending (bounded, re-deniable) |
|
|
150
|
-
|
|
151
|
-
### Skills
|
|
152
|
-
|
|
153
|
-
`skills/cargo-hauler/SKILL.md` is the operating rule set (do not kill
|
|
154
|
-
in-flight cargo, scope with `-p`, await tickets, fail open when the daemon is
|
|
155
|
-
unreachable). `skills/hauler-dashboard/SKILL.tsx` is a rendered skill: the
|
|
156
|
-
build computes its Markdown from the tool and CLI spellings and the App
|
|
157
|
-
resource URI it describes, so the document cannot drift from the surface.
|
|
158
|
-
|
|
159
|
-
### Dashboard
|
|
160
|
-
|
|
161
|
-
`src/mcp/hauler/apps/dashboard.tsx` is the MCP App at
|
|
162
|
-
`ui://cargo-hauler/dashboard.html`, attached to `hauler_status` on hosts that
|
|
163
|
-
render MCP Apps. It shows contention and admission, in-flight and queued
|
|
164
|
-
work, metrics windows, optional kache data, lanes, and history, with a live
|
|
165
|
-
output drawer per ticket.
|
|
166
|
-
|
|
167
|
-

|
|
168
|
-
|
|
169
|
-
## Install
|
|
47
|
+
## Quick start
|
|
170
48
|
|
|
171
|
-
|
|
172
|
-
|
|
49
|
+
Install from npm — the package carries a ready-made plugin for each host and
|
|
50
|
+
its own installer, so nothing else is needed:
|
|
173
51
|
|
|
174
52
|
```sh
|
|
175
|
-
|
|
176
|
-
pnpm run build # artifact/{claude,codex,cursor,portable} + dist/bin
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
Each host pack under `artifact/<host>` is independently installable through
|
|
180
|
-
the framework's installer. The packs are framework-owned; this project ships
|
|
181
|
-
no installer of its own.
|
|
53
|
+
npm install -g cargo-hauler
|
|
182
54
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
# Codex
|
|
188
|
-
pnpm exec agent-bundle install codex --from artifact/codex
|
|
189
|
-
|
|
190
|
-
# Cursor: safe-copy into ~/.cursor/plugins/local/cargo-hauler (default), or
|
|
191
|
-
# stage a local marketplace repository for Customize → Add Plugins from Local Repository
|
|
192
|
-
pnpm exec agent-bundle install cursor --from artifact/cursor --mode local
|
|
193
|
-
pnpm exec agent-bundle install cursor --from artifact/cursor --mode marketplace
|
|
55
|
+
cargo-hauler-install install claude --scope user # Claude Code
|
|
56
|
+
cargo-hauler-install install codex # Codex
|
|
57
|
+
cargo-hauler-install install cursor --mode local # Cursor
|
|
58
|
+
hauler install-shim # optional: cargo from scripts and terminals too
|
|
194
59
|
```
|
|
195
60
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
`
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
installed copy versus the artifact (`current`, `stale`, `version-mismatch`,
|
|
202
|
-
`foreign`, `not-installed`) and, for Cursor, whether the manifest hooks are
|
|
203
|
-
registered. Each pack's `INSTALL.md` carries the same commands with the exact
|
|
204
|
-
compiled names. Restart or reload the host after installing so new sessions
|
|
205
|
-
load the hooks. Per-host notes, hook timeouts, and the optional PATH shim are
|
|
206
|
-
in [docs/install.md](docs/install.md).
|
|
207
|
-
|
|
208
|
-
The first brokered request makes one daemon-start attempt. Hooks cover Cargo
|
|
209
|
-
commands submitted through supported agent shells; the optional PATH shim
|
|
210
|
-
(`node dist/bin/hauler.js install-shim`) also covers Cargo invoked by scripts
|
|
211
|
-
and terminals.
|
|
61
|
+
Restart the host (or reload the window) so new sessions load the hooks. The
|
|
62
|
+
daemon starts on demand with the first brokered request; `hauler status` shows
|
|
63
|
+
what is running, and the `hauler_status` tool opens the dashboard in hosts that
|
|
64
|
+
render MCP Apps. Prefer the hosts' own plugin commands, or building from a
|
|
65
|
+
checkout? See [Install](#install).
|
|
212
66
|
|
|
213
|
-
##
|
|
67
|
+
## Commands and tools
|
|
214
68
|
|
|
215
|
-
`hauler` is the
|
|
216
|
-
`
|
|
217
|
-
|
|
218
|
-
`
|
|
219
|
-
`--json` for the canonical value and `--ndjson` for the render-event stream.
|
|
69
|
+
`hauler` is the command line; agents reach the same operations as MCP tools
|
|
70
|
+
(`hauler_status`, `hauler_await`, …) and hooks rewrite plain `cargo …` into
|
|
71
|
+
`hauler exec` automatically. Every command except `exec`, `daemon`, and
|
|
72
|
+
`install-shim` also accepts `--json` for the machine-readable value.
|
|
220
73
|
|
|
221
74
|
| Command | Behavior |
|
|
222
75
|
| --- | --- |
|
|
@@ -235,27 +88,15 @@ The `hauler` MCP server projects the same operations as `hauler_status`,
|
|
|
235
88
|
`hauler_log`, `hauler_last`, `hauler_await`, `hauler_result`, `hauler_kill`,
|
|
236
89
|
and `hauler_request`, with the same filters as the CLI.
|
|
237
90
|
|
|
238
|
-
##
|
|
239
|
-
|
|
240
|
-
```sh
|
|
241
|
-
pnpm run check # validate + build + typecheck + Effect diagnostics + rstest + route tests
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
`tests/route-unit/` renders the app through the framework compiler with no
|
|
245
|
-
artifact build, at the harness proof levels:
|
|
91
|
+
## Dashboard
|
|
246
92
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
| 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 |
|
|
253
|
-
| packed-stdio | `packed-contract` | the built `artifact/cursor` server as a real process against a live broker, every tool through the wire-contract matrix |
|
|
254
|
-
| workbench-surface | `tests/workbench-surface.test.ts` | what `agent-bundle dev` would show: catalog, provider, lifecycles per host, counts |
|
|
93
|
+
The dashboard is an MCP App (`ui://cargo-hauler/dashboard.html`) attached to
|
|
94
|
+
`hauler_status`. It shows contention and admission, in-flight and queued work
|
|
95
|
+
with live output per ticket, metrics over one-hour, 24-hour, and all-time
|
|
96
|
+
windows, per-command timings, optional kache data, lanes, and history. It
|
|
97
|
+
refreshes itself while open.
|
|
255
98
|
|
|
256
|
-
|
|
257
|
-
(`tests/harness.ts`) and reach it either through the `haulerDaemon` provider
|
|
258
|
-
seam or through `CARGO_HAULER_STATE_DIR`.
|
|
99
|
+

|
|
259
100
|
|
|
260
101
|
## How the broker works
|
|
261
102
|
|
|
@@ -543,6 +384,83 @@ is reported as unavailable and never rejects a request.
|
|
|
543
384
|
|
|
544
385
|

|
|
545
386
|
|
|
387
|
+
## Install
|
|
388
|
+
|
|
389
|
+
Requirements: Node 22.19 or newer, Cargo, and Linux or macOS (Windows is
|
|
390
|
+
experimental: named-pipe transport, no PATH shim).
|
|
391
|
+
|
|
392
|
+
The npm package ships one plugin per host under `artifact/<host>` — the
|
|
393
|
+
Claude Code plugin with its local marketplace, the Codex plugin, and the Cursor
|
|
394
|
+
plugin with its `install.mjs` — plus three executables: `hauler` (the CLI),
|
|
395
|
+
`cargo-hauler` (the routed commands), and `cargo-hauler-install`. Every pack
|
|
396
|
+
also contains an `INSTALL.md` with the exact commands for that host. The
|
|
397
|
+
package declares no runtime dependencies — every library the packs and
|
|
398
|
+
executables use is bundled into them — so `npm install` fetches this one
|
|
399
|
+
tarball and nothing else.
|
|
400
|
+
|
|
401
|
+
### With the bundled installer
|
|
402
|
+
|
|
403
|
+
```sh
|
|
404
|
+
npm install -g cargo-hauler # or run each command as: npx -p cargo-hauler <command>
|
|
405
|
+
|
|
406
|
+
cargo-hauler-install install claude --scope user # user, project, or local
|
|
407
|
+
cargo-hauler-install install codex
|
|
408
|
+
cargo-hauler-install install cursor --mode local # ~/.cursor/plugins/local/cargo-hauler
|
|
409
|
+
cargo-hauler-install install cursor --mode marketplace # stage a local marketplace repo for Customize → Add Plugins from Local Repository
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
`cargo-hauler-install` runs the host's own plugin commands for you (below),
|
|
413
|
+
detects an installed copy with the same version but different content and
|
|
414
|
+
replaces it, and takes `--replace` (alias `--force`) to replace a different
|
|
415
|
+
installed version. `--json` prints the result for scripts.
|
|
416
|
+
|
|
417
|
+
### With the hosts' own plugin commands
|
|
418
|
+
|
|
419
|
+
The same result without the installer, from the package or a build (paths are
|
|
420
|
+
relative to `node_modules/cargo-hauler` or the checkout):
|
|
421
|
+
|
|
422
|
+
```sh
|
|
423
|
+
# Claude Code — a local marketplace plus a plugin install
|
|
424
|
+
cd artifact/claude
|
|
425
|
+
claude plugin marketplace add ./
|
|
426
|
+
claude plugin install cargo-hauler@cargo-hauler-marketplace --scope user
|
|
427
|
+
|
|
428
|
+
# Codex — a local marketplace snapshot
|
|
429
|
+
cd artifact/codex
|
|
430
|
+
codex plugin marketplace add ./
|
|
431
|
+
codex plugin add cargo-hauler@cargo-hauler-marketplace
|
|
432
|
+
|
|
433
|
+
# Cursor — no non-interactive plugin command exists, so the pack ships one
|
|
434
|
+
node artifact/cursor/install.mjs # local plugin (default)
|
|
435
|
+
node artifact/cursor/install.mjs --mode marketplace # local marketplace repository
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Upgrading to a new version: `claude plugin marketplace update cargo-hauler-marketplace
|
|
439
|
+
&& claude plugin update cargo-hauler@cargo-hauler-marketplace`, `codex plugin
|
|
440
|
+
remove … && codex plugin marketplace add ./ && codex plugin add …`, and
|
|
441
|
+
`node artifact/cursor/install.mjs --replace`. `claude plugin update` is
|
|
442
|
+
version-gated, so after a rebuild that did not bump the version use
|
|
443
|
+
`claude plugin uninstall … --keep-data` and install again (the installer does
|
|
444
|
+
this automatically). Restart or reload the host after installing.
|
|
445
|
+
|
|
446
|
+
### From a checkout
|
|
447
|
+
|
|
448
|
+
```sh
|
|
449
|
+
pnpm install
|
|
450
|
+
pnpm run build # artifact/{claude,codex,cursor,portable} + dist/bin
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Then install with either method above from `artifact/<host>`, and run
|
|
454
|
+
`node dist/bin/hauler.js install-shim` for the PATH shim. Building needs the
|
|
455
|
+
repository's dev dependencies (including the agent-bundle framework, pinned as
|
|
456
|
+
a pkg.pr.new preview until it is on npm); using the published package does
|
|
457
|
+
not.
|
|
458
|
+
|
|
459
|
+
The first brokered request makes one daemon-start attempt. Hooks cover Cargo
|
|
460
|
+
commands submitted through supported agent shells; the optional PATH shim
|
|
461
|
+
(`hauler install-shim`) also covers Cargo invoked by scripts and terminals.
|
|
462
|
+
Per-host notes and hook timeouts are in [docs/install.md](docs/install.md).
|
|
463
|
+
|
|
546
464
|
## Configuration
|
|
547
465
|
|
|
548
466
|
| Variable | Default | Meaning |
|
|
@@ -623,7 +541,188 @@ unset, the daemon reads kache's configured local store from
|
|
|
623
541
|
the POSIX PATH shim is unavailable and jobserver integration is disabled.
|
|
624
542
|
- Licensed under MIT.
|
|
625
543
|
|
|
626
|
-
##
|
|
544
|
+
## Architecture
|
|
545
|
+
|
|
546
|
+
<details>
|
|
547
|
+
<summary><strong>How the app is built</strong> — agent-bundle application structure, testing, and development (click to expand)</summary>
|
|
548
|
+
|
|
549
|
+
The plugin is an [agent-bundle](https://github.com/ScriptedAlchemy/agent-bundle)
|
|
550
|
+
application: six MCP tools, a routed CLI, four hook routes, two skills, and a
|
|
551
|
+
browser dashboard, all rendered from one component library through one shared
|
|
552
|
+
layout. This section is for contributors; using cargo-hauler needs none of it.
|
|
553
|
+
|
|
554
|
+
### Application structure
|
|
555
|
+
|
|
556
|
+
Everything an agent sees is a React Server Component rendered by the
|
|
557
|
+
agent-bundle runtime into an Agent Document, then lowered to MCP content, CLI
|
|
558
|
+
Markdown, `--json`, or a host hook envelope. There is no hand-written server,
|
|
559
|
+
argv parser, or string-concatenated Markdown; the `src/` tree is the app.
|
|
560
|
+
|
|
561
|
+
```text
|
|
562
|
+
src/
|
|
563
|
+
layout.tsx the hauler shell around every rendered route
|
|
564
|
+
providers/hauler-daemon.ts request-scoped daemon connection + health probe
|
|
565
|
+
components/ typed components over pure view-models
|
|
566
|
+
mcp/hauler/tools/*.tsx hauler_status, _log, _last, _await, _result, _request
|
|
567
|
+
mcp/hauler/apps/dashboard.tsx the MCP App (ui://cargo-hauler/dashboard.html)
|
|
568
|
+
cli/*.tsx, cli/daemon.ts the routed `cargo-hauler` CLI, same components
|
|
569
|
+
events/{session/start,tool/before,tool/after,stop}.tsx hook routes
|
|
570
|
+
skills/cargo-hauler/SKILL.md, skills/hauler-dashboard/SKILL.tsx
|
|
571
|
+
scripts/hauler.ts the `hauler` process entry hooks rewrite cargo to
|
|
572
|
+
daemon/, client/, hooks/, shim/, lib/ the broker and its libraries
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
#### The shell (`src/layout.tsx`)
|
|
576
|
+
|
|
577
|
+
Every rendered route — MCP tool, CLI command, rendered script — composes
|
|
578
|
+
through one layout, the way a page framework's `layout.tsx` wraps every page:
|
|
579
|
+
|
|
580
|
+
- **Header:** `<DaemonBadge>` prints what the request-start probe proved and
|
|
581
|
+
which state directory it is: `cargo-hauler · daemon running (pid 4021) ·
|
|
582
|
+
2/5 permits +1 riding, 1 queued · 2 lanes busy · up since 3h ago · state dir
|
|
583
|
+
/fast/cache/cargo-hauler`, or `daemon stopped · no socket; it starts on
|
|
584
|
+
demand…`, or `daemon unresponsive · did not accept a connection within
|
|
585
|
+
750ms (machine saturated)…`. When the daemon is another build than the CLI
|
|
586
|
+
or MCP server rendering the document, a second line says so: `cargo-hauler
|
|
587
|
+
· daemon 0.4.2 ≠ cli 0.4.4 — restart it with \`hauler daemon restart\``.
|
|
588
|
+
- **Body:** the route's own document, unchanged. The route keeps its
|
|
589
|
+
`<Agent.Result value>`; the runtime merges it into the shell so
|
|
590
|
+
`structuredContent` and `--json` are exactly what the route declared.
|
|
591
|
+
- **Footer:** `<LineageFooter>` names the conversation the request belongs to
|
|
592
|
+
(`Requested by conversation conv-7f (depth 1 under conv-2a; registry)`),
|
|
593
|
+
read synchronously with `useAgent()`, and stays silent when the host cannot
|
|
594
|
+
place the request rather than guessing.
|
|
595
|
+
- **`_meta.hauler`** on every MCP result: `route`, `surface`, `server`,
|
|
596
|
+
`version`, `daemon: { state, pid? }`, `lineage: { conversation, root, depth } | null`.
|
|
597
|
+
|
|
598
|
+
Event routes are host protocol responses and are never wrapped.
|
|
599
|
+
|
|
600
|
+
#### The daemon provider (`src/providers/hauler-daemon.ts`)
|
|
601
|
+
|
|
602
|
+
One request-context provider mounts `providers.haulerDaemon` for every tool,
|
|
603
|
+
command, event, and script: the resolved `config` (state dir, socket, ledger)
|
|
604
|
+
and a `health` value from one bounded `status` probe:
|
|
605
|
+
|
|
606
|
+
| `health.state` | meaning |
|
|
607
|
+
| --- | --- |
|
|
608
|
+
| `running` | `pid`, `startedAtMs`, `latencyMs`, `running` (permit holders), `riding` (attached), `queued`, `busyLanes`, `maxConcurrent`, and `version` when the daemon states one (on the status report from 0.4.5, by one extra `ping` for older daemons) |
|
|
609
|
+
| `stopped` | `socket-missing` (starts on demand) or `connection-refused` (stale socket) |
|
|
610
|
+
| `unresponsive` | `accept-timeout` (never accepted), `answer-timeout` (accepted, no `status-result`), or `connection-closed` within the probe budget (750 ms for the accept and for the answer); ledger reads still work |
|
|
611
|
+
| `unreachable` | `open-failed` with the errno (`EACCES`, `EMFILE`, …): the socket is present but could not be opened, which is not evidence the daemon is down |
|
|
612
|
+
| `unprobed` | `event-surface`: hooks run on every shell command and skip the probe by design |
|
|
613
|
+
|
|
614
|
+
The provider fails closed on nothing it can observe and fabricates nothing.
|
|
615
|
+
Routes read it through `requestDaemon(context)` / `requestDaemonConfig(context)`;
|
|
616
|
+
tests inject a fixture through the harness `context.providers` seam.
|
|
617
|
+
|
|
618
|
+
#### Components (`src/components/`)
|
|
619
|
+
|
|
620
|
+
Components render view-models and nothing else. The models are pure functions
|
|
621
|
+
in `view-models.ts`, so the MCP document, the CLI Markdown, and a test
|
|
622
|
+
assertion share one derivation.
|
|
623
|
+
|
|
624
|
+
| Component | Renders |
|
|
625
|
+
| --- | --- |
|
|
626
|
+
| `<TicketCard>` | one ticket: headline, attribution, lane, queue position, attach mode, timings, exit, then `<BuildDiagnostics>` and `<LogTail>` |
|
|
627
|
+
| `<TicketList>` | the in-flight and recent tables of status, and the whole of log |
|
|
628
|
+
| `<LaneBoard>` | busy lanes with their leader ticket, its command, and how long it has run |
|
|
629
|
+
| `<AdmissionState>` | permits in use, load, memory clamp, sharing savings; calls out a paused admission gate |
|
|
630
|
+
| `<KacheStats>` | kache coverage and freshness, slowest crates by profile, or an honest "not detected" |
|
|
631
|
+
| `<LogTail>` | the captured output tail, labelled live while the run is in progress |
|
|
632
|
+
| `<FullOutput>` | where the ticket's whole output log lives and how large it is; under `full`, the log itself in code-block chunks |
|
|
633
|
+
| `<BuildDiagnostics>` | an index of cargo `error[E…]`/`warning:` blocks (level / code / message / location) followed by every captured block verbatim |
|
|
634
|
+
| `<DashboardLink>` | where the MCP App lives and how to open it elsewhere |
|
|
635
|
+
| `<TicketGuidance>` | what to do next, one component per ticket status |
|
|
636
|
+
| `<DaemonBadge>`, `<LineageFooter>` | the shell header and footer |
|
|
637
|
+
| `<EmptyState>`, `<UnavailableState>`, `<ErrorState>` | the three non-happy shapes every document may take |
|
|
638
|
+
|
|
639
|
+
`documents.tsx` composes them into one document per hauler result
|
|
640
|
+
(`StatusDocument`, `LogDocument`, `LastDocument`, `ResultDocument`,
|
|
641
|
+
`AwaitDocument`, `RequestDocument`); the MCP tool and the CLI command for the
|
|
642
|
+
same operation render the same document with different command spellings
|
|
643
|
+
(`surface.ts`).
|
|
644
|
+
|
|
645
|
+
#### Streaming (`src/components/streaming.tsx`)
|
|
646
|
+
|
|
647
|
+
`hauler_await` and `hauler_log` are progressive documents. Each is a
|
|
648
|
+
valueless `Agent.Result` container around one `Suspense` boundary:
|
|
649
|
+
|
|
650
|
+
- `<AwaitStream>`: the fallback is the ticket **as it is now** — its live
|
|
651
|
+
output tail and a progress node — rendered before the daemon-side wait
|
|
652
|
+
blocks; the settled child is the ordinary `AwaitDocument`. MCP hosts receive
|
|
653
|
+
the fallback's progress as notifications and the settled value as
|
|
654
|
+
`structuredContent`; the routed CLI updates the terminal in place. Heartbeats
|
|
655
|
+
(queue position, elapsed time, cost estimate) still flow through
|
|
656
|
+
`context.progress`.
|
|
657
|
+
- `<LogStream>`: a "reading the ledger" progress frame, then the listing.
|
|
658
|
+
|
|
659
|
+
#### Attribution and lineage
|
|
660
|
+
|
|
661
|
+
`hauler_request` attributes tickets from the request context: an explicit
|
|
662
|
+
`host`/`session` wins; otherwise the negotiated host and native session are
|
|
663
|
+
used; and when the transport publishes no session id (bare stdio MCP), the
|
|
664
|
+
conversation from `request.lineage` becomes the session of record. That is
|
|
665
|
+
what makes parallel agents' builds attributable in the ledger, the dashboard,
|
|
666
|
+
and `hauler status --session <conversation>` (the `hauler_status` tool takes
|
|
667
|
+
the same filter as its `session` field). Results carry
|
|
668
|
+
`attribution: { host, session, lineage }`.
|
|
669
|
+
|
|
670
|
+
#### Routes
|
|
671
|
+
|
|
672
|
+
| Route | Surface | Document |
|
|
673
|
+
| --- | --- | --- |
|
|
674
|
+
| `tool:hauler/hauler_status` · `cli:status` | queue, lanes, admission, kache, filters | `StatusDocument`; the tool advertises the dashboard App |
|
|
675
|
+
| `tool:hauler/hauler_log` · `cli:log` | recent requests | `LogStream` → `LogDocument` |
|
|
676
|
+
| `tool:hauler/hauler_last` · `cli:last` | most recent request | `LastDocument` |
|
|
677
|
+
| `tool:hauler/hauler_await` · `cli:await` | long-poll a ticket (≤ 2 h) | `AwaitStream` → `AwaitDocument` |
|
|
678
|
+
| `tool:hauler/hauler_result` · `cli:result` | one ticket, live tail while running; `full` renders the whole on-disk output log | `ResultDocument` (`<FullOutput>`) |
|
|
679
|
+
| `tool:hauler/hauler_kill` · `cli:kill` | stop a queued or running ticket | `KillDocument` |
|
|
680
|
+
| `tool:hauler/hauler_request` · `cli:request` | submit a background request | `RequestDocument` |
|
|
681
|
+
| `cli:daemon` | `run` / `start` / `stop` / `status` | plain JSON, exit code from the result |
|
|
682
|
+
| `event:session/start` | new session | daemon state and the no-kill rule as context |
|
|
683
|
+
| `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 |
|
|
684
|
+
| `event:tool/after` | shell tool finished | injects finished background-ticket results once per session |
|
|
685
|
+
| `event:stop` | agent stopping | holds the stop while a foreground ticket is pending (bounded, re-deniable) |
|
|
686
|
+
|
|
687
|
+
#### Skills
|
|
688
|
+
|
|
689
|
+
`skills/cargo-hauler/SKILL.md` is the operating rule set (do not kill
|
|
690
|
+
in-flight cargo, scope with `-p`, await tickets, fail open when the daemon is
|
|
691
|
+
unreachable). `skills/hauler-dashboard/SKILL.tsx` is a rendered skill: the
|
|
692
|
+
build computes its Markdown from the tool and CLI spellings and the App
|
|
693
|
+
resource URI it describes, so the document cannot drift from the surface.
|
|
694
|
+
|
|
695
|
+
#### Dashboard
|
|
696
|
+
|
|
697
|
+
`src/mcp/hauler/apps/dashboard.tsx` is the MCP App at
|
|
698
|
+
`ui://cargo-hauler/dashboard.html`, attached to `hauler_status` on hosts that
|
|
699
|
+
render MCP Apps. It shows contention and admission, in-flight and queued
|
|
700
|
+
work, metrics windows, optional kache data, lanes, and history, with a live
|
|
701
|
+
output drawer per ticket.
|
|
702
|
+
|
|
703
|
+
### Testing
|
|
704
|
+
|
|
705
|
+
```sh
|
|
706
|
+
pnpm run check # validate + build + typecheck + Effect diagnostics + rstest + route tests
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
`tests/route-unit/` renders the app through the framework compiler with no
|
|
710
|
+
artifact build, at the harness proof levels:
|
|
711
|
+
|
|
712
|
+
| Level | Suite | What it proves |
|
|
713
|
+
| --- | --- | --- |
|
|
714
|
+
| route-unit | `routes`, `layout`, `streaming`, `events` | documents, shell metadata, Suspense fallbacks and settled values, lineage attribution, event decisions |
|
|
715
|
+
| cli-dispatch | `cli-dispatch`, `layout` | argv through the routed CLI shell; Markdown wrapped by the shell, `--json` bare |
|
|
716
|
+
| script-dispatch | `script-dispatch` | the `hauler` entry through its `main` envelope as its own process |
|
|
717
|
+
| 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 |
|
|
718
|
+
| packed-stdio | `packed-contract` | the built `artifact/cursor` server as a real process against a live broker, every tool through the wire-contract matrix |
|
|
719
|
+
| workbench-surface | `tests/workbench-surface.test.ts` | what `agent-bundle dev` would show: catalog, provider, lifecycles per host, counts |
|
|
720
|
+
|
|
721
|
+
Daemon-backed cases run a real broker in-process with a fake `cargo`
|
|
722
|
+
(`tests/harness.ts`) and reach it either through the `haulerDaemon` provider
|
|
723
|
+
seam or through `CARGO_HAULER_STATE_DIR`.
|
|
724
|
+
|
|
725
|
+
### Development
|
|
627
726
|
|
|
628
727
|
```sh
|
|
629
728
|
pnpm run dev # agent-bundle workbench with live rebuilds
|
|
@@ -655,3 +754,5 @@ which is why `tests/workbench-surface.test.ts` lives in the plain pool).
|
|
|
655
754
|
`repos/effect` is a read-only subtree containing the Effect v4 source pinned to
|
|
656
755
|
`effect@4.0.0-rc.112`; see `AGENTS.md` before working with Effect code in this
|
|
657
756
|
repository.
|
|
757
|
+
|
|
758
|
+
</details>
|