enabled gated only Governor.check(). The heat model, the heartbeat
piggyback and the list_devices footer all ran unconditionally, so a
governor that was switched off could still report heat, still print
COOLDOWN, and still drive the phone's ACTIVE->COOLDOWN transition while
blocking nothing. tick() now returns early when disabled and clears
carried-over state; to_dict() reports enabled:false with zeroed values;
the list_devices footer is omitted.
Separately, current_intensity was only ever cleared by record_stop(),
whose three callers are an explicit stop, a phone emergency stop and the
dead man's switch. A pattern ending on its own duration told the server
nothing, so heat integrated at the last commanded intensity against idle
hardware forever. Commands now pass their duration through to
record_command() and the intensity expires when it lapses. duration:0
still means run-until-stop. For escalate the expiry is duration +
hold_seconds, and only when hold_seconds > 0, per the hold contract.
Adds tests/verify_governor.py: 22 offline checks over the disabled path,
timed-command expiry, and the enabled-path invariants. Existing
verify_server.py still passes 23/23.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ported from the Android edition (its 'reworded device and tool schemas
in neutral terminology' change):
- Tool descriptions and devices.json now describe hardware in neutral
actuator/engineering terms. Content filters on some LLM platforms
refused to call tools whose schemas contained explicit anatomical
language; the reworded schemas work across providers.
- list_devices now surfaces capability descriptions next to each output
channel (e.g. 'vibrate (two independent eccentric-mass actuators…)')
so the model learns what each channel does from the profile itself.
- All output/pattern tools now declare required=["device"]; pattern
schema defaults adjusted to match the Android edition (intensity 0.5,
duration 60).
- Dual-motor device profiles (Edge, Dolce) document feature_index usage.
- Fixed a stray ')' in nora's rotate capability (typo in the Android
copy of devices.json).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ported from the Android edition and completed for the Python relays
(the Android repo only implemented the phone side in Kotlin):
- models.py / mcp_tools.py: optional feature_index on every output and
pattern tool, passed through to the phone relay.
- relay_client.py: routes targeted writes through buttplug-py's
per-feature API (device.features[i].run_output) and validates the
index up front so a bad one fails the ack with the valid indices
listed, instead of dying silently inside a pattern task.
- termux_relay_v3.py: ScalarCmd entries filtered to the requested
actuator index, same fallback semantics as the Android relay engine.
Lets Claude drive e.g. a Dolce's internal and external motors
independently (feature_index 0 / 1) instead of always both together.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ported from the Android edition's server:
- Add python-multipart: the OAuth endpoints parse credentials with
request.form(), and Starlette needs this package when a client POSTs
the token request as multipart/form-data. Without it those requests
500'd ('the small auth bug').
- governor_enabled now round-trips SQLite's 0/1 as JSON true/false on
read and normalises any truthy input to 0/1 on write. Strict clients
(kotlinx-serialization) reject anything else.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ported from the Android edition's server:
- JSON-RPC notifications (requests without an id, e.g.
notifications/initialized) now get the bare HTTP 202 the MCP spec
requires instead of a malformed JSON-RPC error response, which strict
clients rejected.
- initialize echoes the client's requested protocolVersion when it is a
version we support (2024-11-05 / 2025-03-26 / 2025-06-18) instead of
always claiming 2025-03-26.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Publishes server work that shipped in the Android edition but never made
it to this repo:
- Full OAuth 2.0 flow (discovery metadata, dynamic client registration,
authorize + token endpoints) so claude.ai remote connectors and the
Android app can authenticate per-user instead of relying on the
sole-phone fallback.
- Safety governor: server-side heat model (intensity x time) with
automatic cooldown, per-user overrides via GET/POST /safety/config,
and governor state piggybacked on heartbeat pings so relay clients
can display it.
- SB_REQUIRE_MCP_AUTH env flag for multi-user deployments (disables the
unauthenticated sole-phone fallback).
- requirements-phone.txt and .env.example documenting the new knobs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The March upload accidentally included server/__pycache__/*.pyc (stale
compiled bytecode) and a prebuilt deploy tarball. The README already
instructs users to build their own bundle, so neither belongs in git.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>