The Desktop pool caps local `hermes serve` children and holds each child's
slot lease for its lifetime. Its renderer refreshes lastActiveAt every 60s
for every open socket, so a bot-tile-pinned resident is keepalive-fresh
forever: occupied is not busy, and the pool cannot tell the difference. The
renderer's own turn bookkeeping cannot see cron fires (HERMES_DESKTOP=1 runs
the in-process ticker), messaging turns served by a pooled backend, or a
session blocked on an approval, so it is not a safe proof either.
Add `GET /api/health/idle` (token-gated; whether a turn is running is
activity recon, unlike the public liveness route) returning
`idle: true|false|null` from `hermes_cli/web_server_idle_proof.py`. It
reuses the SSH idle-exit primitive `turn_in_flight()` (running gateway
sessions + running cron jobs) and adds the human-input ledgers: open
server->client requests (`server_requests.open_request_count`) and queued
gateway approvals (`approval.pending_gateway_approval_count`). Any ledger
that cannot be read yields `null`, which the Desktop treats as busy.
Tests: unit invariants over the fail-closed table and the real ledgers, and
a live test that boots three desktop-shaped children (HERMES_DESKTOP=1,
per-child HERMES_HOME, port 0), holds one busy in the cron running-job
ledger, and probes all three over HTTP. RED on base: every child 404s.