cargo-hauler 0.4.5 → 0.4.7
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 +346 -223
- 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 +6489 -6636
- package/artifact/claude/bin/cargo-hauler.mjs +5452 -5210
- 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 +518 -307
- package/artifact/claude/mcp/mcp-hauler-2b8242f9-flight.mjs +5649 -5471
- package/artifact/claude/mcp/mcp-hauler-2b8242f9.mjs +5902 -6060
- package/artifact/claude/mcp-apps/dashboard.html +1 -1
- package/artifact/claude/scripts/hauler.mjs +894 -203
- package/artifact/codex/.codex-plugin/plugin.json +1 -1
- package/artifact/codex/INSTALL.md +1 -1
- package/artifact/codex/bin/cargo-hauler-flight.mjs +6489 -6636
- package/artifact/codex/bin/cargo-hauler.mjs +5452 -5210
- 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 +518 -307
- package/artifact/codex/mcp/mcp-hauler-2b8242f9-flight.mjs +5649 -5471
- package/artifact/codex/mcp/mcp-hauler-2b8242f9.mjs +5902 -6060
- package/artifact/codex/mcp-apps/dashboard.html +1 -1
- package/artifact/codex/scripts/hauler.mjs +894 -203
- package/artifact/cursor/.cursor-plugin/plugin.json +1 -1
- package/artifact/cursor/INSTALL.md +1 -1
- package/artifact/cursor/bin/cargo-hauler-flight.mjs +6489 -6636
- package/artifact/cursor/bin/cargo-hauler.mjs +5452 -5210
- 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 +518 -307
- package/artifact/cursor/install.mjs +1 -1
- package/artifact/cursor/mcp/mcp-hauler-2b8242f9-flight.mjs +5649 -5471
- package/artifact/cursor/mcp/mcp-hauler-2b8242f9.mjs +5902 -6060
- package/artifact/cursor/mcp-apps/dashboard.html +1 -1
- package/artifact/cursor/scripts/hauler.mjs +894 -203
- package/artifact/portable/INSTALL.md +1 -1
- package/artifact/portable/bin/cargo-hauler-flight.mjs +6489 -6636
- package/artifact/portable/bin/cargo-hauler.mjs +5452 -5210
- package/artifact/portable/install.mjs +1 -1
- package/artifact/portable/mcp/mcp-hauler-2b8242f9-flight.mjs +11092 -11241
- package/artifact/portable/mcp/mcp-hauler-2b8242f9.mjs +5895 -6053
- package/artifact/portable/mcp-apps/dashboard.html +1 -1
- package/artifact/portable/plugin.json +1 -1
- package/artifact/portable/scripts/hauler.mjs +894 -203
- package/dist/bin/cargo-hauler-flight.mjs +6489 -6636
- package/dist/bin/cargo-hauler.js +5452 -5210
- package/dist/bin/hauler.js +894 -203
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 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`.
|
|
8
|
+
|
|
9
|
+
## 0.4.6
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 4257982: Survive a CLI or plugin upgrade under a daemon left running from the older install, and add `hauler daemon restart`. The client schemas now default the fields older daemons never send (`outputPath`, `after`), so `hauler status`, `hauler result`, `hauler await`, and the `hauler_*` tools read a 0.4.1 daemon's replies instead of printing a Zod issue array; a reply that still cannot be read fails as `daemon is 0.4.1 (pid N, since 3h ago), this CLI is 0.4.4 — restart it with \`hauler daemon restart\`` with the first mismatch (`active[0].outputPath expected string, received undefined`) on a second line, and `hauler daemon status` says the same. The status report carries the daemon `version` (older daemons are asked with one `ping`), `hauler status --json` exposes it as `daemonVersion`, and every document header — plus the status summary — warns `daemon 0.4.2 ≠ cli 0.4.4 — restart it with \`hauler daemon restart\`` while the versions differ. `hauler daemon restart` sends the graceful stop, waits up to 5 s for the old pid to exit, starts a daemon from this install, and prints both pids and versions (`previousPid` in the JSON result; exit `1` when the old daemon did not exit, in which case nothing is started). Tickets in flight at a restart are not handed over: the new daemon marks them `killed` with `orphaned by daemon restart`, and `hauler result` / `hauler_result` explain that instead of showing a plain kill. The shell header ends with `state dir <path>` so a moved `CARGO_HAULER_STATE_DIR` is visible on every command. (#75)
|
|
14
|
+
|
|
3
15
|
## 0.4.5
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -2,218 +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:
|
|
45
|
-
`cargo-hauler · daemon running (pid 4021) · 2/5 permits +1 riding, 1 queued
|
|
46
|
-
· 2 lanes busy · up since 3h ago`, or `daemon stopped · no socket; it starts
|
|
47
|
-
on demand…`, or `daemon unresponsive · did not accept a connection within
|
|
48
|
-
750ms (machine saturated)…`.
|
|
49
|
-
- **Body:** the route's own document, unchanged. The route keeps its
|
|
50
|
-
`<Agent.Result value>`; the runtime merges it into the shell so
|
|
51
|
-
`structuredContent` and `--json` are exactly what the route declared.
|
|
52
|
-
- **Footer:** `<LineageFooter>` names the conversation the request belongs to
|
|
53
|
-
(`Requested by conversation conv-7f (depth 1 under conv-2a; registry)`),
|
|
54
|
-
read synchronously with `useAgent()`, and stays silent when the host cannot
|
|
55
|
-
place the request rather than guessing.
|
|
56
|
-
- **`_meta.hauler`** on every MCP result: `route`, `surface`, `server`,
|
|
57
|
-
`version`, `daemon: { state, pid? }`, `lineage: { conversation, root, depth } | null`.
|
|
58
|
-
|
|
59
|
-
Event routes are host protocol responses and are never wrapped.
|
|
60
|
-
|
|
61
|
-
### The daemon provider (`src/providers/hauler-daemon.ts`)
|
|
62
|
-
|
|
63
|
-
One request-context provider mounts `providers.haulerDaemon` for every tool,
|
|
64
|
-
command, event, and script: the resolved `config` (state dir, socket, ledger)
|
|
65
|
-
and a `health` value from one bounded `status` probe:
|
|
66
|
-
|
|
67
|
-
| `health.state` | meaning |
|
|
68
|
-
| --- | --- |
|
|
69
|
-
| `running` | `pid`, `startedAtMs`, `latencyMs`, `running` (permit holders), `riding` (attached), `queued`, `busyLanes`, `maxConcurrent` |
|
|
70
|
-
| `stopped` | `socket-missing` (starts on demand) or `connection-refused` (stale socket) |
|
|
71
|
-
| `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 |
|
|
72
|
-
| `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 |
|
|
73
|
-
| `unprobed` | `event-surface`: hooks run on every shell command and skip the probe by design |
|
|
74
|
-
|
|
75
|
-
The provider fails closed on nothing it can observe and fabricates nothing.
|
|
76
|
-
Routes read it through `requestDaemon(context)` / `requestDaemonConfig(context)`;
|
|
77
|
-
tests inject a fixture through the harness `context.providers` seam.
|
|
78
|
-
|
|
79
|
-
### Components (`src/components/`)
|
|
80
|
-
|
|
81
|
-
Components render view-models and nothing else. The models are pure functions
|
|
82
|
-
in `view-models.ts`, so the MCP document, the CLI Markdown, and a test
|
|
83
|
-
assertion share one derivation.
|
|
84
|
-
|
|
85
|
-
| Component | Renders |
|
|
86
|
-
| --- | --- |
|
|
87
|
-
| `<TicketCard>` | one ticket: headline, attribution, lane, queue position, attach mode, timings, exit, then `<BuildDiagnostics>` and `<LogTail>` |
|
|
88
|
-
| `<TicketList>` | the in-flight and recent tables of status, and the whole of log |
|
|
89
|
-
| `<LaneBoard>` | busy lanes with their leader ticket, its command, and how long it has run |
|
|
90
|
-
| `<AdmissionState>` | permits in use, load, memory clamp, sharing savings; calls out a paused admission gate |
|
|
91
|
-
| `<KacheStats>` | kache coverage and freshness, slowest crates by profile, or an honest "not detected" |
|
|
92
|
-
| `<LogTail>` | the captured output tail, labelled live while the run is in progress |
|
|
93
|
-
| `<FullOutput>` | where the ticket's whole output log lives and how large it is; under `full`, the log itself in code-block chunks |
|
|
94
|
-
| `<BuildDiagnostics>` | an index of cargo `error[E…]`/`warning:` blocks (level / code / message / location) followed by every captured block verbatim |
|
|
95
|
-
| `<DashboardLink>` | where the MCP App lives and how to open it elsewhere |
|
|
96
|
-
| `<TicketGuidance>` | what to do next, one component per ticket status |
|
|
97
|
-
| `<DaemonBadge>`, `<LineageFooter>` | the shell header and footer |
|
|
98
|
-
| `<EmptyState>`, `<UnavailableState>`, `<ErrorState>` | the three non-happy shapes every document may take |
|
|
99
|
-
|
|
100
|
-
`documents.tsx` composes them into one document per hauler result
|
|
101
|
-
(`StatusDocument`, `LogDocument`, `LastDocument`, `ResultDocument`,
|
|
102
|
-
`AwaitDocument`, `RequestDocument`); the MCP tool and the CLI command for the
|
|
103
|
-
same operation render the same document with different command spellings
|
|
104
|
-
(`surface.ts`).
|
|
105
|
-
|
|
106
|
-
### Streaming (`src/components/streaming.tsx`)
|
|
107
|
-
|
|
108
|
-
`hauler_await` and `hauler_log` are progressive documents. Each is a
|
|
109
|
-
valueless `Agent.Result` container around one `Suspense` boundary:
|
|
110
|
-
|
|
111
|
-
- `<AwaitStream>`: the fallback is the ticket **as it is now** — its live
|
|
112
|
-
output tail and a progress node — rendered before the daemon-side wait
|
|
113
|
-
blocks; the settled child is the ordinary `AwaitDocument`. MCP hosts receive
|
|
114
|
-
the fallback's progress as notifications and the settled value as
|
|
115
|
-
`structuredContent`; the routed CLI updates the terminal in place. Heartbeats
|
|
116
|
-
(queue position, elapsed time, cost estimate) still flow through
|
|
117
|
-
`context.progress`.
|
|
118
|
-
- `<LogStream>`: a "reading the ledger" progress frame, then the listing.
|
|
119
|
-
|
|
120
|
-
### Attribution and lineage
|
|
121
|
-
|
|
122
|
-
`hauler_request` attributes tickets from the request context: an explicit
|
|
123
|
-
`host`/`session` wins; otherwise the negotiated host and native session are
|
|
124
|
-
used; and when the transport publishes no session id (bare stdio MCP), the
|
|
125
|
-
conversation from `request.lineage` becomes the session of record. That is
|
|
126
|
-
what makes parallel agents' builds attributable in the ledger, the dashboard,
|
|
127
|
-
and `hauler status --session <conversation>` (the `hauler_status` tool takes
|
|
128
|
-
the same filter as its `session` field). Results carry
|
|
129
|
-
`attribution: { host, session, lineage }`.
|
|
130
|
-
|
|
131
|
-
### Routes
|
|
132
|
-
|
|
133
|
-
| Route | Surface | Document |
|
|
134
|
-
| --- | --- | --- |
|
|
135
|
-
| `tool:hauler/hauler_status` · `cli:status` | queue, lanes, admission, kache, filters | `StatusDocument`; the tool advertises the dashboard App |
|
|
136
|
-
| `tool:hauler/hauler_log` · `cli:log` | recent requests | `LogStream` → `LogDocument` |
|
|
137
|
-
| `tool:hauler/hauler_last` · `cli:last` | most recent request | `LastDocument` |
|
|
138
|
-
| `tool:hauler/hauler_await` · `cli:await` | long-poll a ticket (≤ 2 h) | `AwaitStream` → `AwaitDocument` |
|
|
139
|
-
| `tool:hauler/hauler_result` · `cli:result` | one ticket, live tail while running; `full` renders the whole on-disk output log | `ResultDocument` (`<FullOutput>`) |
|
|
140
|
-
| `tool:hauler/hauler_kill` · `cli:kill` | stop a queued or running ticket | `KillDocument` |
|
|
141
|
-
| `tool:hauler/hauler_request` · `cli:request` | submit a background request | `RequestDocument` |
|
|
142
|
-
| `cli:daemon` | `run` / `start` / `stop` / `status` | plain JSON, exit code from the result |
|
|
143
|
-
| `event:session/start` | new session | daemon state and the no-kill rule as context |
|
|
144
|
-
| `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 |
|
|
145
|
-
| `event:tool/after` | shell tool finished | injects finished background-ticket results once per session |
|
|
146
|
-
| `event:stop` | agent stopping | holds the stop while a foreground ticket is pending (bounded, re-deniable) |
|
|
147
|
-
|
|
148
|
-
### Skills
|
|
149
|
-
|
|
150
|
-
`skills/cargo-hauler/SKILL.md` is the operating rule set (do not kill
|
|
151
|
-
in-flight cargo, scope with `-p`, await tickets, fail open when the daemon is
|
|
152
|
-
unreachable). `skills/hauler-dashboard/SKILL.tsx` is a rendered skill: the
|
|
153
|
-
build computes its Markdown from the tool and CLI spellings and the App
|
|
154
|
-
resource URI it describes, so the document cannot drift from the surface.
|
|
155
|
-
|
|
156
|
-
### Dashboard
|
|
157
|
-
|
|
158
|
-
`src/mcp/hauler/apps/dashboard.tsx` is the MCP App at
|
|
159
|
-
`ui://cargo-hauler/dashboard.html`, attached to `hauler_status` on hosts that
|
|
160
|
-
render MCP Apps. It shows contention and admission, in-flight and queued
|
|
161
|
-
work, metrics windows, optional kache data, lanes, and history, with a live
|
|
162
|
-
output drawer per ticket.
|
|
163
|
-
|
|
164
|
-

|
|
165
|
-
|
|
166
|
-
## Install
|
|
167
|
-
|
|
168
|
-
Requirements: Node 22.19 or newer, Cargo, and Linux or macOS (Windows is
|
|
169
|
-
experimental: named-pipe transport, no PATH shim).
|
|
170
|
-
|
|
171
|
-
```sh
|
|
172
|
-
pnpm install
|
|
173
|
-
pnpm run build # artifact/{claude,codex,cursor,portable} + dist/bin
|
|
174
|
-
```
|
|
47
|
+
## Quick start
|
|
175
48
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
no installer of its own.
|
|
49
|
+
Install from npm — the package carries a ready-made plugin for each host and
|
|
50
|
+
its own installer, so nothing else is needed:
|
|
179
51
|
|
|
180
52
|
```sh
|
|
181
|
-
|
|
182
|
-
pnpm exec agent-bundle install claude --from artifact/claude --scope user
|
|
53
|
+
npm install -g cargo-hauler
|
|
183
54
|
|
|
184
|
-
#
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
#
|
|
188
|
-
# stage a local marketplace repository for Customize → Add Plugins from Local Repository
|
|
189
|
-
pnpm exec agent-bundle install cursor --from artifact/cursor --mode local
|
|
190
|
-
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
|
|
191
59
|
```
|
|
192
60
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
`
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
installed copy versus the artifact (`current`, `stale`, `version-mismatch`,
|
|
199
|
-
`foreign`, `not-installed`) and, for Cursor, whether the manifest hooks are
|
|
200
|
-
registered. Each pack's `INSTALL.md` carries the same commands with the exact
|
|
201
|
-
compiled names. Restart or reload the host after installing so new sessions
|
|
202
|
-
load the hooks. Per-host notes, hook timeouts, and the optional PATH shim are
|
|
203
|
-
in [docs/install.md](docs/install.md).
|
|
204
|
-
|
|
205
|
-
The first brokered request makes one daemon-start attempt. Hooks cover Cargo
|
|
206
|
-
commands submitted through supported agent shells; the optional PATH shim
|
|
207
|
-
(`node dist/bin/hauler.js install-shim`) also covers Cargo invoked by scripts
|
|
208
|
-
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).
|
|
209
66
|
|
|
210
|
-
##
|
|
67
|
+
## Commands and tools
|
|
211
68
|
|
|
212
|
-
`hauler` is the
|
|
213
|
-
`
|
|
214
|
-
|
|
215
|
-
`
|
|
216
|
-
`--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.
|
|
217
73
|
|
|
218
74
|
| Command | Behavior |
|
|
219
75
|
| --- | --- |
|
|
@@ -225,34 +81,22 @@ executable beside it (`dist/bin/cargo-hauler.js` in the package,
|
|
|
225
81
|
| `hauler result <ticket> [--full]` | A stored ticket; running tickets include a live output tail. The document names the full on-disk output log (`Full output: <path> (size)`) and `--json` carries it as `request.outputPath`; `--full` prints that whole log instead of the tail (the last ~768 KiB when it does not fit, with the path for the rest). |
|
|
226
82
|
| `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. |
|
|
227
83
|
| `hauler request [--session ID] [--host HOST] [--cwd DIR] [--after TICKET …] -- <cargo …>` | Submit a background request and return its ticket, with where it landed in its lane (`queued behind cc-3281 (~13m)`, `waiting for cc-3281`, or `attached to cc-3281`). `--after` works as for `exec`. |
|
|
228
|
-
| `hauler daemon <run\|start\|stop\|status>` | Manage the daemon lifecycle. |
|
|
84
|
+
| `hauler daemon <run\|start\|stop\|status\|restart>` | Manage the daemon lifecycle. `restart` sends the graceful stop, waits up to 5 s for the old pid to exit, then starts a daemon from this install and prints both (`restarted: pid 741314 (0.4.1) → pid 742001 (0.4.4)`); a daemon that has not exited by then is reported, not killed, and nothing is started (exit `1`). Tickets in flight at the restart are not handed over: the new daemon marks them `killed` with the error `orphaned by daemon restart`. |
|
|
229
85
|
| `hauler install-shim [--dir DIR] [--real-cargo PATH] [--force]` | Install the optional PATH shim. |
|
|
230
86
|
|
|
231
87
|
The `hauler` MCP server projects the same operations as `hauler_status`,
|
|
232
88
|
`hauler_log`, `hauler_last`, `hauler_await`, `hauler_result`, `hauler_kill`,
|
|
233
89
|
and `hauler_request`, with the same filters as the CLI.
|
|
234
90
|
|
|
235
|
-
##
|
|
236
|
-
|
|
237
|
-
```sh
|
|
238
|
-
pnpm run check # validate + build + typecheck + Effect diagnostics + rstest + route tests
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
`tests/route-unit/` renders the app through the framework compiler with no
|
|
242
|
-
artifact build, at the harness proof levels:
|
|
91
|
+
## Dashboard
|
|
243
92
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
| 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 |
|
|
250
|
-
| packed-stdio | `packed-contract` | the built `artifact/cursor` server as a real process against a live broker, every tool through the wire-contract matrix |
|
|
251
|
-
| 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.
|
|
252
98
|
|
|
253
|
-
|
|
254
|
-
(`tests/harness.ts`) and reach it either through the `haulerDaemon` provider
|
|
255
|
-
seam or through `CARGO_HAULER_STATE_DIR`.
|
|
99
|
+

|
|
256
100
|
|
|
257
101
|
## How the broker works
|
|
258
102
|
|
|
@@ -454,6 +298,23 @@ error `stalled: no CPU for Nm after owner disconnected; killed automatically`.
|
|
|
454
298
|
tickets (`--bg`, `hauler_request`) have no streaming connection and are only
|
|
455
299
|
ever flagged.
|
|
456
300
|
|
|
301
|
+
Tickets do not survive a daemon restart. `hauler daemon restart` (or `stop`
|
|
302
|
+
then `start`) ends every queued or running ticket: the cargo processes die
|
|
303
|
+
with the daemon, and the new daemon's first ledger pass marks each of them
|
|
304
|
+
`killed` with the error `orphaned by daemon restart`, so `hauler result cc-N`
|
|
305
|
+
answers `cc-N killed — orphaned by daemon restart: the daemon stopped while it
|
|
306
|
+
was in flight and does not hand runs over; resubmit if the work is still
|
|
307
|
+
needed` rather than looking like a failure of the command itself. Restart when
|
|
308
|
+
the CLI or plugin was upgraded under a running daemon: every document then
|
|
309
|
+
carries `daemon 0.4.2 ≠ cli 0.4.4 — restart it with \`hauler daemon restart\``,
|
|
310
|
+
and a reply the newer client still cannot read is reported as `daemon is 0.4.2
|
|
311
|
+
(pid N, since 3h ago), this CLI is 0.4.4 — restart it with \`hauler daemon
|
|
312
|
+
restart\`` with the first schema mismatch on a second line — never as a raw
|
|
313
|
+
validation dump. The client schemas default the fields older daemons never
|
|
314
|
+
send (`outputPath`, `after`), so a plain version difference alone does not
|
|
315
|
+
break `status`, `result`, or `await`; finish or `hauler kill` what is in
|
|
316
|
+
flight before restarting if the work matters.
|
|
317
|
+
|
|
457
318
|
The `tool/after` route checks the session's background tickets — `--bg`,
|
|
458
319
|
`hauler_request`, and synchronous requests the client converted to a ticket
|
|
459
320
|
— and, on the first tool call after one finishes, adds its result to the
|
|
@@ -523,6 +384,80 @@ is reported as unavailable and never rejects a request.
|
|
|
523
384
|
|
|
524
385
|

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