Addresses PR #78 review feedback:
- New `has_backend(name)` on the backend package + abstract
`BottleBackend.is_available()` on each concrete subclass.
Replaces inline `shutil.which("docker") is None` checks in
docker/cleanup.py:178 and smolmachines/enumerate.py:73.
Docker → `shutil.which("docker") is not None`; smolmachines
→ `smolvm.is_available()`. Cross-backend `enumerate_active_
agents()` skips backends whose `is_available()` is False so a
docker-only host doesn't fail when iterating past
smolmachines (and vice versa).
- Move docker `enumerate_active` + parser helpers out of
cleanup.py into a new `backend/docker/enumerate.py`, mirroring
the smolmachines/enumerate.py layout. cleanup.py is now
purely about prepare_cleanup / cleanup; the active-listing
concern owns its own file.
- Drop the `ActiveAgent = ActiveBottle` alias in dashboard.py.
The canonical name is `ActiveAgent` (the thing running inside
a bottle is always called "agent" in this codebase; the bottle
is the container). Renamed `enumerate_active_bottles` →
`enumerate_active_agents` to match.
Tests:
- `test_backend_selection.TestEnumerateActiveAgents
.test_skips_unavailable_backends` locks down the
`is_available()`-gated iteration.
- New `TestHasBackend` covers `has_backend("docker")` consulting
the backend's `is_available`, and unknown-name → False.
- Existing tests follow the rename; the docker-availability-
side-effect test in `test_docker_enumerate_active` moves up
to the cross-backend layer (where the gate lives now).
607 unit tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>