Switch the bundled ACP tooling from full-CLI bundling to bridge-only
bundling. The staging scripts now install the bridge JS trees with
`npm --omit=optional`, which skips the SDK/codex platform packages that
vendor the native claude/codex CLIs — the bundled resource drops from
~586MB to ~56MB of pure JS, and ad-hoc codesigning of vendored Mach-O
binaries is no longer needed.
The bridges instead run the user's own harness CLI: at spawn time the
desktop resolves `claude`/`codex` from PATH and exports
CLAUDE_CODE_EXECUTABLE / CODEX_PATH (neither bridge falls back to PATH
itself), driven by a new `bridge_cli_env_var` catalog field. A value
already present in the desktop's environment wins, and per-agent env
overrides still apply afterwards.
Because the app once again depends on a user-installed CLI, this
reinstates the machinery that 55a80e5c retired: the CliMissing
availability gate, PATH-based auth probes, curl CLI install commands,
and the Doctor's CLI-missing copy and CLI-path row. The Doctor's
"bundled" badge now reads an explicit `adapter_ships_with_app` catalog
field, since inferring it from empty install-command lists breaks once
claude/codex regain CLI install commands.
The lock drops the native*/npmOs/npmCpu/npmLibc fields (trees are
platform-independent, but stay per-target so pins can be bumped
independently), install validation asserts no platform package slipped
into the tree, and freshness stamps gain STAMP_INSTALL_MODE=bridge-only
so stale full-CLI caches are reinstalled rather than reused. The
harness-clis.json manifest and its resolution path are removed; the
prepare script deletes the stale file from previously staged resource
dirs.
Verified: desktop cargo tests (1421), buzz-acp tests, clippy + fmt on
both, tsc, biome, desktop unit tests (2791), doctor-states +
doctor-cta-screenshots Playwright specs, file-size/px guards, and
end-to-end staging on aarch64-apple-darwin (bridge wrappers report
0.58.1/1.1.2, no Mach-O in the tree, idempotent re-run).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Since the ACP bundles vendor full native CLIs (Claude Code inside
@anthropic-ai/claude-agent-sdk-*, codex inside @openai/codex-*), agent
sessions never touch a user-installed claude/codex. Yet the auth probes
still resolved those CLIs from the user's PATH, and a missing user CLI
put the runtime behind a CliMissing availability gate — blocking agents
that would have run fine on the bundle. Point the probes at the vendored
CLIs and delete the gate plus everything downstream of it (curl-pipe CLI
install commands, Doctor's user-CLI path row, the cli_missing nudge card).
The vendored CLIs are deliberately NOT staged into resources/acp/bin:
that dir is the highest-priority segment of the agent-spawn PATH, and
CLIs there would shadow the user's (possibly newer) claude/codex inside
every session. Instead a probe-only manifest maps CLI names to paths
inside the staged node trees.
- prepare-acp-tools-resource.sh reads nativePackage/nativeExecutable
from acp-tools.lock.json, verifies the vendored binary exists in the
staged tree (chmod +x), and writes harness-clis.json next to
node-runtime.json: {"clis":[{id,cli,path}]} with resource-root-relative
paths. The manifest is gitignored like its sibling.
- acp_tools::bundled_harness_cli() resolves a CLI name through the
manifest — bare names only, relative non-escaping paths only, must be
an executable file. Absent manifest/entry degrades to None.
- discovery::resolve_probe_binary() tries the bundled CLI first, then
the user's PATH — auth probes in the discovery sweep and
readiness::cli_login_requirements both go through it. Auth state now
reflects the CLI the sessions actually use, while dev builds without
a staged bundle keep working via the PATH fallback.
- classify_runtime: a resolving adapter is Available unconditionally;
underlying_cli now only disambiguates AdapterMissing vs NotInstalled.
CliMissing is removed from the desktop enum (runtime-only, never
persisted) and from the TS union; buzz-acp's wire mirror keeps the
variant so payloads from older desktops still parse (AdapterOutdated
precedent), while the FE nudge validator rejects the retired literal
so stale JSON can't render a card the UI has no branch for.
- claude/codex catalog entries drop underlying_cli and the curl-pipe
cli_install_commands; login provisioning copy stays as-is.
- UI: cli_missing branches removed from Doctor (StatusIcon, RuntimeRow,
user-CLI path row), SetupStep, persona pickers ("(CLI missing)"
suffix + sort rank), AgentDefinitionDialog warning, and the nudge
card; retired the cli_missing nudge screenshot spec.
Verification:
- cargo test --lib (desktop/src-tauri): 1436 passed, 0 failed (new
bundled_harness_cli manifest/escape/absent tests, probe-runs-without-
underlying-CLI readiness test, adapter-implies-Available discovery test)
- cargo test -p buzz-acp --lib: 497 passed (cli_missing round-trip kept)
- cargo clippy --lib --tests -D warnings (both crates): clean;
cargo fmt --check (both crates): clean
- tsc --noEmit: clean; biome check + file-size/px-text/pubkey guards: pass
- pnpm test (desktop): 2792 passed, 0 failed (new validator-rejects-
cli_missing test)
- playwright doctor-states.spec.ts + doctor-cta-screenshots.spec.ts:
12 passed (Doctor shows no CLI path row; nudge cards unaffected)
- prepare-acp-tools-resource.sh: manifest written; vendored
claude --version → 2.1.205 (Claude Code); vendored
codex login status → exit 0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Buzz desktop spawned the claude-agent-acp and codex-acp bridges from
whatever the user had installed globally — an unpinned `npm install -g`
surface with no integrity checking, which produced stale-bridge drift
(the deprecated @zed-industries/codex-acp 0.16.x gate) and
missing-tool failures. Bundle both bridges as app resources instead,
pinned per target:
- desktop/acp-tools.lock.json pins @agentclientprotocol/claude-agent-acp
0.58.1 and @agentclientprotocol/codex-acp 1.1.2 (npm `latest` at time
of commit) for the four supported targets, with integrity hashes and
Block Artifactory tarballs for the package, its claude-agent-sdk /
@openai/codex dependency, and the per-target native package.
- desktop/scripts/update-acp-tools-lock.mjs regenerates the lock from
the registry's `latest` dist-tags, failing loudly on any unresolvable
package — never silently pinning an older version. Ranged
dependencies (codex-acp's ^0.144.0) resolve to the highest matching
version when `npm view` returns an array.
- desktop/scripts/ensure-acp-tools.sh installs the locked tools into a
shared dev cache (~/Library/Caches/buzz-dev/acp-tools), validates
versions + integrity against the lock, and stamps staged binaries
next to the shared bin dir so any lock change — including a revert —
forces a re-stage; binaries no longer in the lock are pruned.
- desktop/scripts/prepare-acp-tools-resource.sh stages the vendored npm
trees + node wrapper shims into desktop/src-tauri/resources/acp,
writes the node-runtime.json manifest for the app's Node.js doctor
check, and ad-hoc signs every nested Mach-O (file(1) scan — the codex
package vendors rg, zsh, and codex-code-mode-host beyond the main
CLIs) so Gatekeeper doesn't kill them in local builds.
- desktop/scripts/lib/acp-node-wrapper.sh is the single wrapper-shim
generator shared by both scripts, so the dev-cache and bundled
wrappers (and the Node major they enforce) cannot drift.
- Wiring: `just dev` / `just staging` stage the resources and export
BUZZ_ACP_TOOLS_DIR; `just desktop-release-build` stages per target
before `tauri build`; `just bump-acp-tools` reruns the lock updater;
tauri.conf.json bundles resources/acp; staged artifacts are
gitignored with a .gitkeep placeholder.
Runtime resolution of the staged dir follows in the next commit. The
sprout-releases internal pipeline will need the same staging step
before its `tauri build`; that lives in a separate repo.
Ports the build-time half of the Staged implementation in
block/builderbot#876 (branch commits 16b115a7 bundle feature, f5c6bba9
re-stage after lock revert, 288fa7eb shared node wrapper lib, adea4017
node-runtime manifest, 5124302a sign all nested Mach-Os, de6a9782
ranged-dependency updater fix), itself ported from squareup/berd
f07df1d2 + 1db993fb + 24d7518b + 07087303 + 737e33a5.
Verified: update-acp-tools-lock.mjs regenerated the lock against the
Block registry (byte-identical pins to the donor lock; both packages
confirmed at npm `latest`); fresh stage installs both tools and the
staged wrappers report 0.58.1 / 1.1.2; no-op re-run performs zero npm
installs; a stamp/lock mismatch forces a re-stage and stray binaries
are pruned; all five staged Mach-Os pass `codesign --verify`; cargo
check on desktop/src-tauri passes with the new resources entry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
## Summary
- Activate lefthook pre-commit and pre-push hooks that have existed in `lefthook.yml` since the initial desktop app but were never wired up.
- Add `just hooks` recipe: sets `core.hooksPath = .hooks` and runs `lefthook install --force` to generate hook scripts.
- Wire hook installation into `scripts/dev-setup.sh` so hooks activate automatically on `just setup`.
- Enable `parallel: true` for pre-commit hooks (pre-push already had it) — all 5 format/lint checks run simultaneously.
- Add `just mobile-fmt` recipe (`dart format .`) and `just fmt-all` recipe (Rust root + Tauri Rust + Dart) as one-shot formatters.
- `.hooks/` is gitignored since lefthook generates machine-specific scripts.
- Update `AGENTS.md`: document pre-commit/pre-push hooks in Quality Gates, add `just fmt-all` and `just hooks` usage, upgrade worktree fmt gotcha from CI note to commit blocker, add `just mobile-fmt` to mobile commands; also backfill CLI-first updates (`SPROUT_AUTH_TAG`, complete exit codes, `--format compact` flag position, two new gotchas).
All hook commands delegate to `just` recipes as the single source of truth.